import type { Transaction } from "../generated/kysely-tailordb";
import {
  SiteNotFoundError,
  InvalidNameError,
  DuplicateNameError,
  InvalidTypeError,
  MissingRequiredFieldsError,
  InvalidCountryError,
  InvalidTimezoneError,
} from "../lib/errors.generated";
import { ok, err } from "@tailor-platform/erp-kit/module";

export type UpdateSiteInput = {
  siteId: string;
  name?: string;
  type?: string;
  street?: string;
  city?: string;
  state?: string | null;
  postalCode?: string;
  country?: string;
  timezone?: string;
};

const VALID_SITE_TYPES = ["OFFICE", "WAREHOUSE", "STORE", "FACTORY", "DISTRIBUTION_CENTER"];

function isValidCountryCode(code: string): boolean {
  try {
    const displayNames = new Intl.DisplayNames(["en"], { type: "region" });
    const name = displayNames.of(code);
    return name !== undefined && name !== code;
  } catch {
    return false;
  }
}

function isValidTimezone(tz: string): boolean {
  try {
    Intl.DateTimeFormat(undefined, { timeZone: tz });
    return true;
  } catch {
    return false;
  }
}
/**
 * Function: updateSite
 *
 * Modifies the details of an existing site, including its name, type, address,
 * and timezone. Only provided fields are updated.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateSiteInput & Omit<Partial<CF>, "status">,
) {
  const {
    siteId,
    name,
    type,
    street,
    city,
    state,
    postalCode,
    country,
    timezone,
    ...customFields
  } = input;

  // 1. Find site with forUpdate()
  const existingSite = await db
    .selectFrom("Site")
    .selectAll()
    .where("id", "=", siteId)
    .forUpdate()
    .executeTakeFirst();

  if (!existingSite) {
    return err(new SiteNotFoundError(siteId));
  }

  // 2. If name provided and empty, error
  if (name?.trim() === "") {
    return err(new InvalidNameError(name));
  }

  // 3. If name changed, check uniqueness in company
  if (name !== undefined && name !== existingSite.name) {
    const duplicate = await db
      .selectFrom("Site")
      .selectAll()
      .where("name", "=", name)
      .where("companyId", "=", existingSite.companyId)
      .forUpdate()
      .executeTakeFirst();

    if (duplicate) {
      return err(new DuplicateNameError(name));
    }
  }

  // 4. If type provided, validate in allowed list
  if (type !== undefined && !VALID_SITE_TYPES.includes(type)) {
    return err(new InvalidTypeError(type));
  }

  // 5. If address fields provided and empty, error
  if (street?.trim() === "") {
    return err(new MissingRequiredFieldsError("street"));
  }
  if (city?.trim() === "") {
    return err(new MissingRequiredFieldsError("city"));
  }
  if (postalCode?.trim() === "") {
    return err(new MissingRequiredFieldsError("postalCode"));
  }
  if (country?.trim() === "") {
    return err(new MissingRequiredFieldsError("country"));
  }

  // 6. If country provided, validate
  if (country !== undefined && !isValidCountryCode(country)) {
    return err(new InvalidCountryError(country));
  }

  // 7. If timezone provided, validate
  if (timezone !== undefined && !isValidTimezone(timezone)) {
    return err(new InvalidTimezoneError(timezone));
  }

  // 8. Build update object — strip reserved model columns from customFields
  const RESERVED_KEYS = new Set([
    "id",
    "companyId",
    "status",
    "name",
    "type",
    "street",
    "city",
    "state",
    "postalCode",
    "country",
    "timezone",
    "createdAt",
    "updatedAt",
  ]);
  const safeCustomFields: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(customFields)) {
    if (!RESERVED_KEYS.has(key)) {
      safeCustomFields[key] = value;
    }
  }

  const updates: Record<string, unknown> = {
    ...safeCustomFields,
    updatedAt: new Date(),
  };

  if (name !== undefined) updates.name = name;
  if (type !== undefined) updates.type = type;
  if (street !== undefined) updates.street = street;
  if (city !== undefined) updates.city = city;
  if (state !== undefined) updates.state = state;
  if (postalCode !== undefined) updates.postalCode = postalCode;
  if (country !== undefined) updates.country = country;
  if (timezone !== undefined) updates.timezone = timezone;

  // 9. Update
  const site = await db
    .updateTable("Site")
    .set(updates)
    .where("id", "=", siteId)
    .returningAll()
    .executeTakeFirst();

  return ok({ site: site! });
}
