import { Alepha, z } from "alepha";
import { describe, expect, it } from "vitest";
import { $entity, $repository, DrizzleKitProvider, db } from "../core/index.ts";
import { AlephaOrmPostgres } from "../postgres/index.ts";

// Test 1: z.enum with mode: "text" (should map to TEXT column)
const textEnumEntity = $entity({
  name: "text_enum_test",
  schema: z.object({
    id: db.primaryKey(),
    status: z.enum(["pending", "active", "archived"]).meta({ mode: "text" }),
    role: z.enum(["user", "admin", "moderator"]).meta({ mode: "text" }),
  }),
});

// Test 2: z.enum without mode (should create real PG ENUM type)
const pgEnumEntity = $entity({
  name: "pg_enum_test",
  schema: z.object({
    id: db.primaryKey(),
    status: z.enum(["draft", "published", "deleted"]),
    priority: z.enum(["low", "medium", "high"]),
  }),
});

// Test 3: Mixed text and pg enum in the same table
const mixedEnumEntity = $entity({
  name: "mixed_enum_test",
  schema: z.object({
    id: db.primaryKey(),
    textStatus: z.enum(["open", "closed"]).meta({ mode: "text" }),
    pgStatus: z.enum(["new", "in_progress", "done"]),
  }),
});

// Test 4: Shared enum with custom name across two tables
const sharedEnumEntity1 = $entity({
  name: "shared_enum_test_1",
  schema: z.object({
    id: db.primaryKey(),
    status: z
      .enum(["enabled", "disabled"])
      .meta({ name: "shared_status_enum" }),
  }),
});

const sharedEnumEntity2 = $entity({
  name: "shared_enum_test_2",
  schema: z.object({
    id: db.primaryKey(),
    status: z
      .enum(["enabled", "disabled"])
      .meta({ name: "shared_status_enum" }),
  }),
});

// Test 5: Conflicting enum - same name but different values (should throw error)
const conflictEnumEntity1 = $entity({
  name: "conflict_enum_test_1",
  schema: z.object({
    id: db.primaryKey(),
    status: z.enum(["a", "b", "c"]).meta({ name: "conflict_status_enum" }),
  }),
});

const conflictEnumEntity2 = $entity({
  name: "conflict_enum_test_2",
  schema: z.object({
    id: db.primaryKey(),
    status: z.enum(["a", "b", "d"]).meta({ name: "conflict_status_enum" }), // Different value!
  }),
});

describe("enums - z.enum with mode: 'text' (TEXT column)", () => {
  it("should create TEXT columns for z.enum fields with mode: 'text'", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository = $repository(textEnumEntity);
    }

    const app = alepha.inject(App);
    const drizzleKit = alepha.inject(DrizzleKitProvider);

    await alepha.start();

    // Check generated SQL
    const { statements: sql } = await drizzleKit.generateMigration(
      app.repository.provider,
    );

    expect(sql).toContainEqual(expect.stringContaining("CREATE TABLE"));
    expect(sql).toContainEqual(expect.stringContaining("text_enum_test"));
    // mode: "text" should map to TEXT, not a real ENUM type
    expect(sql.some((s) => s.includes('"status" text'))).toBe(true);
    expect(sql.some((s) => s.includes('"role" text'))).toBe(true);
  });

  it("should allow inserting and querying with text enum values", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository = $repository(textEnumEntity);
    }

    const app = alepha.inject(App);
    await alepha.start();

    const created = await app.repository.create({
      status: "pending",
      role: "user",
    });

    expect(created.status).toBe("pending");
    expect(created.role).toBe("user");
    expect(created.id).toBeDefined();

    const found = await app.repository.findOne({ where: { id: created.id } });
    expect(found?.status).toBe("pending");
    expect(found?.role).toBe("user");
  });
});

describe("enums - z.enum (real PG ENUM type)", () => {
  it("should create real PostgreSQL ENUM types for z.enum fields", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository = $repository(pgEnumEntity);
    }

    const app = alepha.inject(App);
    const drizzleKit = alepha.inject(DrizzleKitProvider);

    await alepha.start();

    // Check generated SQL
    const { statements: sql } = await drizzleKit.generateMigration(
      app.repository.provider,
    );

    expect(sql).toContainEqual(expect.stringContaining("CREATE TABLE"));
    expect(sql).toContainEqual(expect.stringContaining("pg_enum_test"));
    // z.enum without mode should create real ENUM types
    expect(
      sql.some((s) => s.includes("CREATE TYPE") || s.includes("DO $$ BEGIN")),
    ).toBe(true);
  });

  it("should allow inserting and querying with pg enum values", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository = $repository(pgEnumEntity);
    }

    const app = alepha.inject(App);
    await alepha.start();

    const created = await app.repository.create({
      status: "draft",
      priority: "high",
    });

    expect(created.status).toBe("draft");
    expect(created.priority).toBe("high");
    expect(created.id).toBeDefined();

    const found = await app.repository.findOne({ where: { id: created.id } });
    expect(found?.status).toBe("draft");
    expect(found?.priority).toBe("high");
  });
});

describe("enums - mixed text and pg enum in same table", () => {
  it("should handle both TEXT and PG ENUM types in the same table", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository = $repository(mixedEnumEntity);
    }

    const app = alepha.inject(App);
    const drizzleKit = alepha.inject(DrizzleKitProvider);

    await alepha.start();

    // Check generated SQL
    const { statements: sql } = await drizzleKit.generateMigration(
      app.repository.provider,
    );

    expect(sql).toContainEqual(expect.stringContaining("CREATE TABLE"));
    expect(sql).toContainEqual(expect.stringContaining("mixed_enum_test"));
    // textStatus should be TEXT
    expect(
      sql.some(
        (s) =>
          s.includes('"text_status" text') || s.includes('"textStatus" text'),
      ),
    ).toBe(true);
    // pgStatus should create an ENUM type
    expect(
      sql.some((s) => s.includes("CREATE TYPE") || s.includes("DO $$ BEGIN")),
    ).toBe(true);
  });

  it("should allow inserting and querying with mixed enum values", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository = $repository(mixedEnumEntity);
    }

    const app = alepha.inject(App);
    await alepha.start();

    const created = await app.repository.create({
      textStatus: "open",
      pgStatus: "new",
    });

    expect(created.textStatus).toBe("open");
    expect(created.pgStatus).toBe("new");
    expect(created.id).toBeDefined();

    const found = await app.repository.findOne({ where: { id: created.id } });
    expect(found?.textStatus).toBe("open");
    expect(found?.pgStatus).toBe("new");
  });
});

describe("enums - shared enum with custom name across tables", () => {
  it("should reuse the same PG ENUM type across multiple tables", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository1 = $repository(sharedEnumEntity1);
      repository2 = $repository(sharedEnumEntity2);
    }

    const app = alepha.inject(App);
    const drizzleKit = alepha.inject(DrizzleKitProvider);

    await alepha.start();

    // Check generated SQL
    const { statements: sql } = await drizzleKit.generateMigration(
      app.repository1.provider,
    );

    // Count how many times the shared_status_enum is created
    // It should only be created once, even though it's used in two tables
    const enumCreations = sql.filter(
      (s) =>
        s.includes("shared_status_enum") &&
        (s.includes("CREATE TYPE") || s.includes("DO $$ BEGIN")),
    );

    // Should only create the enum type once
    expect(enumCreations.length).toBeLessThanOrEqual(1);

    // Both tables should exist
    expect(sql.some((s) => s.includes("shared_enum_test_1"))).toBe(true);
    expect(sql.some((s) => s.includes("shared_enum_test_2"))).toBe(true);
  });

  it("should allow both tables to use the shared enum values", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    class App {
      repository1 = $repository(sharedEnumEntity1);
      repository2 = $repository(sharedEnumEntity2);
    }

    const app = alepha.inject(App);
    await alepha.start();

    const created1 = await app.repository1.create({
      status: "enabled",
    });

    const created2 = await app.repository2.create({
      status: "disabled",
    });

    expect(created1.status).toBe("enabled");
    expect(created2.status).toBe("disabled");

    const found1 = await app.repository1.findOne({
      where: { id: created1.id },
    });
    const found2 = await app.repository2.findOne({
      where: { id: created2.id },
    });

    expect(found1?.status).toBe("enabled");
    expect(found2?.status).toBe("disabled");
  });
});

describe("enums - conflict detection with different values", () => {
  it("should throw error when same enum name has different values", async () => {
    const alepha = Alepha.create().with(AlephaOrmPostgres);

    expect(() =>
      alepha.with(() => ({
        r1: $repository(conflictEnumEntity1),
        r2: $repository(conflictEnumEntity2),
      })),
    ).toThrow(/Enum name conflict/i);
  });
});
