import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidNameError,
  InvalidTypeError,
  CompanyNotFoundError,
  CompanyInactiveError,
  DuplicateNameError,
  MissingRequiredFieldsError,
  InvalidCountryError,
  InvalidTimezoneError,
} from "../lib/errors.generated";
import { activeCompany, activeSite, inactiveCompany } from "../testing/fixtures";
import { run } from "./createSite";

describe("createSite", () => {
  const validInput = {
    name: "New Site",
    type: "OFFICE",
    companyId: activeCompany.id,
    street: "100 Main St",
    city: "Tokyo",
    postalCode: "100-0001",
    country: "JP",
    timezone: "Asia/Tokyo",
  };

  it("creates site in ACTIVE status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdSite = {
      id: "new-site-id",
      ...validInput,
      state: null,
      status: "ACTIVE",
      createdAt: new Date(),
      updatedAt: null,
    };

    spies.select
      .mockReturnValueOnce(activeCompany) // find company
      .mockReturnValueOnce(undefined); // no duplicate name
    spies.insert.mockReturnValue(createdSite);

    const result = await run(db, validInput);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.status).toBe("ACTIVE");
      expect(result.value.site.name).toBe("New Site");
    }
  });

  it("throws when name is empty", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { ...validInput, name: "" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidNameError);
    }
  });

  it("throws when type is invalid", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(db, { ...validInput, type: "INVALID" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidTypeError);
    }
  });

  it("throws when company does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined); // company not found

    const result = await run(db, { ...validInput, companyId: "nonexistent" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CompanyNotFoundError);
    }
  });

  it("throws when company is not ACTIVE", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(inactiveCompany);

    const result = await run(db, { ...validInput, companyId: inactiveCompany.id });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CompanyInactiveError);
    }
  });

  it("throws when site name already exists in same company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(activeCompany) // find company
      .mockReturnValueOnce(activeSite); // duplicate name found

    const result = await run(db, { ...validInput, name: activeSite.name });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(DuplicateNameError);
    }
  });

  it("allows same site name in different companies", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const otherCompany = { ...activeCompany, id: "company-other" };
    const createdSite = {
      id: "new-site-id",
      name: activeSite.name,
      type: "OFFICE",
      companyId: otherCompany.id,
      street: "100 Main St",
      city: "Tokyo",
      state: null,
      postalCode: "100-0001",
      country: "JP",
      timezone: "Asia/Tokyo",
      status: "ACTIVE",
      createdAt: new Date(),
      updatedAt: null,
    };

    spies.select
      .mockReturnValueOnce(otherCompany) // find company
      .mockReturnValueOnce(undefined); // no duplicate in this company
    spies.insert.mockReturnValue(createdSite);

    const result = await run(db, {
      ...validInput,
      name: activeSite.name,
      companyId: otherCompany.id,
    });

    expect(result.ok).toBe(true);
  });

  it("throws when address street is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, street: "" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldsError);
    }
  });

  it("throws when address city is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, city: "" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldsError);
    }
  });

  it("throws when address postalCode is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, postalCode: "" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldsError);
    }
  });

  it("throws when address country is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, country: "" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldsError);
    }
  });

  it("creates site without state/province", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdSite = {
      id: "new-site-id",
      ...validInput,
      state: null,
      status: "ACTIVE",
      createdAt: new Date(),
      updatedAt: null,
    };

    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValue(createdSite);

    const result = await run(db, validInput);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.state).toBeNull();
    }
  });

  it("creates site with state/province", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdSite = {
      id: "new-site-id",
      ...validInput,
      state: "Tokyo",
      status: "ACTIVE",
      createdAt: new Date(),
      updatedAt: null,
    };

    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValue(createdSite);

    const result = await run(db, { ...validInput, state: "Tokyo" });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.state).toBe("Tokyo");
    }
  });

  it("throws when country code is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, country: "XX" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidCountryError);
    }
  });

  it("throws when timezone is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activeCompany).mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, timezone: "Invalid/Timezone" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidTimezoneError);
    }
  });
});
