import { relations } from "drizzle-orm";
import {
	bigint,
	boolean,
	integer,
	json,
	pgTable,
	text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { backups } from "./backups";
import { environments } from "./environment";
import { mounts } from "./mount";
import { server } from "./server";
import {
	applicationStatus,
	type EndpointSpecSwarm,
	EndpointSpecSwarmSchema,
	type HealthCheckSwarm,
	HealthCheckSwarmSchema,
	type LabelsSwarm,
	LabelsSwarmSchema,
	type NetworkSwarm,
	NetworkSwarmSchema,
	type PlacementSwarm,
	PlacementSwarmSchema,
	type RestartPolicySwarm,
	RestartPolicySwarmSchema,
	type ServiceModeSwarm,
	ServiceModeSwarmSchema,
	sqldNode,
	type UpdateConfigSwarm,
	UpdateConfigSwarmSchema,
} from "./shared";
import {
	DATABASE_PASSWORD_MESSAGE,
	DATABASE_PASSWORD_REGEX,
	generateAppName,
} from "./utils";

export const libsql = pgTable("libsql", {
	libsqlId: text("libsqlId")
		.notNull()
		.primaryKey()
		.$defaultFn(() => nanoid()),
	name: text("name").notNull(),
	appName: text("appName")
		.notNull()
		.$defaultFn(() => generateAppName("libsql"))
		.unique(),
	description: text("description"),
	databaseUser: text("databaseUser").notNull(),
	databasePassword: text("databasePassword").notNull(),
	sqldNode: sqldNode("sqldNode").notNull().default("primary"),
	sqldPrimaryUrl: text("sqldPrimaryUrl"),
	enableNamespaces: boolean("enableNamespaces").notNull().default(false),
	dockerImage: text("dockerImage").notNull(),
	command: text("command"),
	env: text("env"),
	// RESOURCES
	memoryReservation: text("memoryReservation"),
	memoryLimit: text("memoryLimit"),
	cpuReservation: text("cpuReservation"),
	cpuLimit: text("cpuLimit"),
	//
	externalPort: integer("externalPort"),
	externalGRPCPort: integer("externalGRPCPort"),
	externalAdminPort: integer("externalAdminPort"),
	applicationStatus: applicationStatus("applicationStatus")
		.notNull()
		.default("idle"),
	healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
	restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
	placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
	updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
	rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
	modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
	labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
	networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
	stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "number" }),
	endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
	replicas: integer("replicas").default(1).notNull(),
	createdAt: text("createdAt")
		.notNull()
		.$defaultFn(() => new Date().toISOString()),

	environmentId: text("environmentId")
		.notNull()
		.references(() => environments.environmentId, { onDelete: "cascade" }),
	serverId: text("serverId").references(() => server.serverId, {
		onDelete: "cascade",
	}),
});

export const libsqlRelations = relations(libsql, ({ one, many }) => ({
	environment: one(environments, {
		fields: [libsql.environmentId],
		references: [environments.environmentId],
	}),
	backups: many(backups),
	mounts: many(mounts),
	server: one(server, {
		fields: [libsql.serverId],
		references: [server.serverId],
	}),
}));

const createSchema = createInsertSchema(libsql, {
	libsqlId: z.string(),
	name: z.string().min(1),
	appName: z.string().min(1),
	createdAt: z.string(),
	databaseUser: z.string().min(1),
	databasePassword: z.string().regex(DATABASE_PASSWORD_REGEX, {
		message: DATABASE_PASSWORD_MESSAGE,
	}),
	sqldNode: z.enum(sqldNode.enumValues),
	sqldPrimaryUrl: z.string().nullable(),
	enableNamespaces: z.boolean().default(false),
	dockerImage: z
		.string()
		.default("ghcr.io/tursodatabase/libsql-server:v0.24.32"),
	command: z.string().optional(),
	env: z.string().optional(),
	memoryReservation: z.string().optional(),
	memoryLimit: z.string().optional(),
	cpuReservation: z.string().optional(),
	cpuLimit: z.string().optional(),
	environmentId: z.string(),
	applicationStatus: z.enum(["idle", "running", "done", "error"]),
	externalPort: z.number(),
	externalGRPCPort: z.number(),
	externalAdminPort: z.number(),
	description: z.string().optional(),
	serverId: z.string().optional(),
	healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
	restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
	placementSwarm: PlacementSwarmSchema.nullable(),
	updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
	rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
	modeSwarm: ServiceModeSwarmSchema.nullable(),
	labelsSwarm: LabelsSwarmSchema.nullable(),
	networkSwarm: NetworkSwarmSchema.nullable(),
	stopGracePeriodSwarm: z.number().nullable(),
	endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
});

export const apiCreateLibsql = createSchema
	.pick({
		name: true,
		appName: true,
		dockerImage: true,
		environmentId: true,
		description: true,
		databaseUser: true,
		databasePassword: true,
		sqldNode: true,
		sqldPrimaryUrl: true,
		enableNamespaces: true,
		serverId: true,
	})
	.required()
	.superRefine((data, ctx) => {
		if (data.sqldNode === "replica" && !data.sqldPrimaryUrl) {
			ctx.addIssue({
				code: z.ZodIssueCode.custom,
				path: ["sqldPrimaryUrl"],
				message: "sqldPrimaryUrl is required when sqldNode is 'replica'.",
			});
		}
		if (data.sqldNode !== "replica" && data.sqldPrimaryUrl) {
			ctx.addIssue({
				code: z.ZodIssueCode.custom,
				path: ["sqldPrimaryUrl"],
				message:
					"sqldPrimaryUrl should not be provided when sqldNode is not 'replica'.",
			});
		}
	});

export const apiFindOneLibsql = z.object({
	libsqlId: z.string().min(1),
});

export const apiChangeLibsqlStatus = createSchema
	.pick({
		libsqlId: true,
		applicationStatus: true,
	})
	.required();

export const apiSaveEnvironmentVariablesLibsql = createSchema
	.pick({
		libsqlId: true,
		env: true,
	})
	.required();

export const apiSaveExternalPortsLibsql = createSchema
	.pick({
		libsqlId: true,
		externalPort: true,
		externalGRPCPort: true,
		externalAdminPort: true,
	})
	.required({ libsqlId: true })
	.superRefine((data, ctx) => {
		if (
			data.externalPort === null &&
			data.externalGRPCPort === null &&
			data.externalAdminPort === null
		) {
			ctx.addIssue({
				code: z.ZodIssueCode.custom,
				message:
					"Either externalPort, externalGRPCPort or externalAdminPort must be provided.",
				path: ["externalPort", "externalGRPCPort", "externalAdminPort"],
			});
		}
	});

export const apiDeployLibsql = createSchema
	.pick({
		libsqlId: true,
	})
	.required();

export const apiResetLibsql = createSchema
	.pick({
		libsqlId: true,
		appName: true,
	})
	.required();

export const apiUpdateLibsql = createSchema
	.partial()
	.extend({
		libsqlId: z.string().min(1),
	})
	.omit({ serverId: true });

export const apiRebuildLibsql = createSchema
	.pick({
		libsqlId: true,
	})
	.required();
