import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  SiteNotFoundError,
  InvalidNameError,
  DuplicateNameError,
  InvalidTypeError,
  InvalidCountryError,
  InvalidTimezoneError,
  MissingRequiredFieldsError,
} from "../lib/errors.generated";
import { activeSite, inactiveSite } from "../testing/fixtures";
import { run } from "./updateSite";

describe("updateSite", () => {
  it("updates site name", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...activeSite, name: "New Name", updatedAt: new Date() };

    mock.enqueueResults(
      [activeSite], // find site
      [], // no duplicate name
      [updatedSite],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { siteId: activeSite.id, name: "New Name" }),
    );

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

  it("updates site address fields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = {
      ...activeSite,
      street: "999 New St",
      city: "Osaka",
      postalCode: "530-0002",
      updatedAt: new Date(),
    };

    mock.enqueueResults([activeSite], [updatedSite]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        siteId: activeSite.id,
        street: "999 New St",
        city: "Osaka",
        postalCode: "530-0002",
      }),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.street).toBe("999 New St");
      expect(result.value.site.city).toBe("Osaka");
    }
  });

  it("updates site type", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...activeSite, type: "WAREHOUSE", updatedAt: new Date() };

    mock.enqueueResults([activeSite], [updatedSite]);

    const result = await mock.withTx((trx) =>
      run(trx, { siteId: activeSite.id, type: "WAREHOUSE" }),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.type).toBe("WAREHOUSE");
    }
  });

  it("updates site timezone", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...activeSite, timezone: "America/New_York", updatedAt: new Date() };

    mock.enqueueResults([activeSite], [updatedSite]);

    const result = await mock.withTx((trx) =>
      run(trx, { siteId: activeSite.id, timezone: "America/New_York" }),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.timezone).toBe("America/New_York");
    }
  });

  it("returns error when site does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) => run(trx, { siteId: "nonexistent" }));

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

  it("returns error when name is empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, name: "" }));

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

  it("returns error when name already exists in same company", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const otherSite = { ...activeSite, id: "site-other", name: "Existing Name" };

    mock.enqueueResults(
      [activeSite], // find site
      [otherSite], // duplicate name found
    );

    const result = await mock.withTx((trx) =>
      run(trx, { siteId: activeSite.id, name: "Existing Name" }),
    );

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

  it("returns error when type is invalid", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, type: "INVALID" }));

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

  it("returns error when country code is invalid", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, country: "XX" }));

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

  it("returns error when timezone is invalid", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) =>
      run(trx, { siteId: activeSite.id, timezone: "Invalid/Timezone" }),
    );

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

  it("updates site country", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...activeSite, country: "US", updatedAt: new Date() };

    mock.enqueueResults([activeSite], [updatedSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, country: "US" }));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.country).toBe("US");
    }
  });

  it("returns error when street is set to empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, street: "" }));

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

  it("returns error when city is set to empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, city: "" }));

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

  it("returns error when postalCode is set to empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, postalCode: "" }));

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

  it("returns error when country is set to empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activeSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, country: "" }));

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

  it("updates state/province field", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...activeSite, state: "Osaka", updatedAt: new Date() };

    mock.enqueueResults([activeSite], [updatedSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, state: "Osaka" }));

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

  it("clears state/province field", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...activeSite, state: null, updatedAt: new Date() };

    mock.enqueueResults([activeSite], [updatedSite]);

    const result = await mock.withTx((trx) => run(trx, { siteId: activeSite.id, state: null }));

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

  it("updates address fields on INACTIVE site", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedSite = { ...inactiveSite, street: "New Street", updatedAt: new Date() };

    mock.enqueueResults([inactiveSite], [updatedSite]);

    const result = await mock.withTx((trx) =>
      run(trx, { siteId: inactiveSite.id, street: "New Street" }),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.site.street).toBe("New Street");
    }
  });
});
