import type { SupabaseAuthSession } from "./supabase-auth";
import type {
  BambooContactCandidate,
  Contact,
  ContactArea,
  Country,
  TaxCategory,
  TaxEvent,
} from "./tax-utils";

const supabaseUrl = String(import.meta.env.VITE_SUPABASE_URL ?? "")
  .trim()
  .replace(/\/+$/, "");
const publishableKey = String(import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY ?? "").trim();
const anonKey = String(import.meta.env.VITE_SUPABASE_ANON_KEY ?? "").trim();
const supabaseKey = publishableKey || anonKey;
const bambooContactWorkflowUrl = String(
  import.meta.env.VITE_BAMBOOHR_CONTACT_WEBHOOK_URL ?? "",
).trim();
const googleCalendarWorkflowUrl = String(
  import.meta.env.VITE_GOOGLE_CALENDAR_WEBHOOK_URL ?? "",
).trim();

type CountryRow = {
  id: string;
  code: string;
  name: string;
  is_regional: boolean;
  sort_order: number;
};

type ObligationRow = {
  id: string;
  due_date: string;
  description: string;
  country_id: string;
  category: TaxCategory;
  source?: string;
};

type DeliveryStatusRow = {
  obligation_id: string;
  has_sent_notification: boolean;
  first_sent_at: string | null;
  sent_notification_count: number;
};

export type ObligationDeliveryStatus = {
  obligationId: string;
  hasSentNotification: boolean;
  firstSentAt: string | null;
  sentNotificationCount: number;
};

type ContactRow = {
  id: string;
  country_id: string;
  full_name: string;
  email: string | null;
  job_title: string | null;
  area: ContactArea;
  department: string | null;
  division: string | null;
  location: string | null;
  source: string | null;
  bamboo_employee_id: string | null;
  bamboo_employee_number: string | null;
  bamboo_status: string | null;
  email_enabled: boolean;
  google_chat_enabled: boolean;
  calendar_enabled: boolean;
};

export type NewObligation = {
  date: string;
  text: string;
  countryId: string;
  category: TaxCategory;
};

export type NewContact = {
  countryId: string;
  name: string;
  email: string;
  role: string;
  area: ContactArea;
  department?: string;
  division?: string;
  location?: string;
  source?: "manual" | "bamboohr" | string;
  bambooEmployeeId?: string;
  bambooEmployeeNumber?: string;
  bambooStatus?: string;
  emailEnabled: boolean;
  googleChatEnabled: boolean;
  calendarEnabled: boolean;
};

type BambooSearchResponse = {
  ok?: boolean;
  message?: string;
  candidates?: BambooContactCandidate[];
};

export async function loadObligationDeliveryStatuses(
  session: SupabaseAuthSession,
): Promise<ObligationDeliveryStatus[]> {
  const rows = await request<DeliveryStatusRow[]>(
    session,
    "/rest/v1/rpc/tax_obligation_delivery_status",
    { method: "POST", body: "{}" },
  );

  return rows.map((row) => ({
    obligationId: row.obligation_id,
    hasSentNotification: row.has_sent_notification,
    firstSentAt: row.first_sent_at,
    sentNotificationCount: Number(row.sent_notification_count ?? 0),
  }));
}

export async function loadTaxData(session: SupabaseAuthSession): Promise<{
  countries: Country[];
  events: TaxEvent[];
  contacts: Contact[];
}> {
  const countries = await getCountries(session);
  const countryMap = new Map(countries.map((country) => [country.id, country]));

  const [eventRows, contactRows, deliveryRows] = await Promise.all([
    request<ObligationRow[]>(
      session,
      "/rest/v1/tax_obligations?select=id,due_date,description,country_id,category,source&active=eq.true&order=due_date.asc,description.asc",
    ),
    request<ContactRow[]>(
      session,
      "/rest/v1/tax_contacts?select=id,country_id,full_name,email,job_title,area,department,division,location,source,bamboo_employee_id,bamboo_employee_number,bamboo_status,email_enabled,google_chat_enabled,calendar_enabled&active=eq.true&order=full_name.asc",
    ),
    loadObligationDeliveryStatuses(session),
  ]);

  const deliveryMap = new Map(deliveryRows.map((row) => [row.obligationId, row]));

  const events = eventRows.map((row) => {
    const country = countryMap.get(row.country_id);
    if (!country) throw new Error("Una obligación tiene un país inválido.");
    const delivery = deliveryMap.get(row.id);
    return {
      id: row.id,
      date: row.due_date,
      text: row.description,
      countryId: row.country_id,
      country: country.name,
      countryCode: country.code,
      category: row.category,
      source: row.source,
      hasSentNotification: delivery?.hasSentNotification ?? false,
      firstSentAt: delivery?.firstSentAt ?? null,
      sentNotificationCount: delivery?.sentNotificationCount ?? 0,
    } satisfies TaxEvent;
  });

  const contacts = contactRows.map((row) => mapContactRow(row, countryMap));

  return { countries, events, contacts };
}

export async function createObligation(
  session: SupabaseAuthSession,
  input: NewObligation,
): Promise<TaxEvent> {
  assertNotPastDate(input.date);

  const [row] = await request<ObligationRow[]>(session, "/rest/v1/tax_obligations", {
    method: "POST",
    headers: { Prefer: "return=representation" },
    body: JSON.stringify({
      due_date: input.date,
      description: input.text.trim(),
      country_id: input.countryId,
      category: input.category,
      source: "app",
      is_historical: false,
    }),
  });

  const countries = await getCountries(session);
  const country = countries.find((item) => item.id === row.country_id);
  if (!country) throw new Error("No se encontró el país de la obligación.");

  const payload = {
    obligation_id: row.id,
    due_date: row.due_date,
  };
  void notifyGoogleCalendar("obligation.created", payload);

  return {
    id: row.id,
    date: row.due_date,
    text: row.description,
    countryId: row.country_id,
    country: country.name,
    countryCode: country.code,
    category: row.category,
    source: row.source,
    hasSentNotification: false,
    firstSentAt: null,
    sentNotificationCount: 0,
  };
}

export async function updateObligation(
  session: SupabaseAuthSession,
  id: string,
  input: NewObligation,
): Promise<TaxEvent> {
  assertNotPastDate(input.date);

  const [row] = await request<ObligationRow[]>(
    session,
    `/rest/v1/tax_obligations?id=eq.${encodeURIComponent(id)}`,
    {
      method: "PATCH",
      headers: { Prefer: "return=representation" },
      body: JSON.stringify({
        due_date: input.date,
        description: input.text.trim(),
        country_id: input.countryId,
        category: input.category,
      }),
    },
  );

  if (!row) throw new Error("No se encontró la obligación.");

  const countries = await getCountries(session);
  const country = countries.find((item) => item.id === row.country_id);
  if (!country) throw new Error("No se encontró el país de la obligación.");

  const payload = {
    obligation_id: row.id,
    due_date: row.due_date,
  };
  void notifyGoogleCalendar("obligation.updated", payload);

  return {
    id: row.id,
    date: row.due_date,
    text: row.description,
    countryId: row.country_id,
    country: country.name,
    countryCode: country.code,
    category: row.category,
    source: row.source,
    hasSentNotification: false,
    firstSentAt: null,
    sentNotificationCount: 0,
  };
}

export async function removeObligation(session: SupabaseAuthSession, id: string): Promise<void> {
  await request(session, `/rest/v1/tax_obligations?id=eq.${encodeURIComponent(id)}`, {
    method: "PATCH",
    headers: { Prefer: "return=minimal" },
    body: JSON.stringify({ active: false }),
  });
  const payload = { obligation_id: id };
  void notifyGoogleCalendar("obligation.deleted", payload);
}

export async function createContact(
  session: SupabaseAuthSession,
  input: NewContact,
): Promise<Contact> {
  const [row] = await request<ContactRow[]>(session, "/rest/v1/tax_contacts", {
    method: "POST",
    headers: { Prefer: "return=representation" },
    body: JSON.stringify(contactPayload(input)),
  });

  const countries = await getCountries(session);
  const countryMap = new Map(countries.map((country) => [country.id, country]));
  const created = mapContactRow(row, countryMap);

  return created;
}

export async function updateContact(
  session: SupabaseAuthSession,
  id: string,
  input: NewContact,
): Promise<Contact> {
  const [row] = await request<ContactRow[]>(
    session,
    `/rest/v1/tax_contacts?id=eq.${encodeURIComponent(id)}`,
    {
      method: "PATCH",
      headers: { Prefer: "return=representation" },
      body: JSON.stringify(contactPayload(input)),
    },
  );

  if (!row) throw new Error("No se encontró el contacto.");

  const countries = await getCountries(session);
  const countryMap = new Map(countries.map((country) => [country.id, country]));
  const updated = mapContactRow(row, countryMap);

  return updated;
}

export async function removeContact(session: SupabaseAuthSession, id: string): Promise<void> {
  await request(session, `/rest/v1/tax_contacts?id=eq.${encodeURIComponent(id)}`, {
    method: "PATCH",
    headers: { Prefer: "return=minimal" },
    body: JSON.stringify({ active: false }),
  });
}

export async function searchBambooContacts(
  session: SupabaseAuthSession,
  fullName: string,
): Promise<BambooContactCandidate[]> {
  if (!bambooContactWorkflowUrl) {
    throw new Error("Falta configurar el webhook de BambooHR.");
  }

  const controller = new AbortController();
  const timeout = window.setTimeout(() => controller.abort(), 300_000);

  try {
    const response = await fetch(bambooContactWorkflowUrl, {
      method: "POST",
      headers: { "Content-Type": "text/plain;charset=UTF-8" },
      body: JSON.stringify({
        action: "search_contact",
        full_name: fullName.trim(),
        requested_by: session.user.email,
        source: "seguimiento-impuestos-glm",
      }),
      signal: controller.signal,
    });

    if (!response.ok) {
      throw new Error("No pudimos consultar BambooHR.");
    }

    const payload = (await response.json()) as BambooSearchResponse;
    if (payload.ok === false) {
      throw new Error(payload.message || "No pudimos consultar BambooHR.");
    }

    return Array.isArray(payload.candidates) ? payload.candidates : [];
  } catch (error) {
    if (error instanceof DOMException && error.name === "AbortError") {
      throw new Error("La búsqueda tardó demasiado. Inténtalo nuevamente.");
    }
    throw error;
  } finally {
    window.clearTimeout(timeout);
  }
}

async function getCountries(session: SupabaseAuthSession): Promise<Country[]> {
  const rows = await request<CountryRow[]>(
    session,
    "/rest/v1/tax_countries?select=id,code,name,is_regional,sort_order&active=eq.true&order=sort_order.asc,name.asc",
  );
  return rows.map((row) => ({
    id: row.id,
    code: row.code,
    name: row.name,
    isRegional: row.is_regional,
    sortOrder: row.sort_order,
  }));
}

async function request<T = unknown>(
  session: SupabaseAuthSession,
  path: string,
  init: RequestInit = {},
): Promise<T> {
  const headers = new Headers(init.headers);
  headers.set("apikey", supabaseKey);
  headers.set("Authorization", `Bearer ${session.accessToken}`);
  headers.set("Content-Type", "application/json");

  const response = await fetch(`${supabaseUrl}${path}`, {
    ...init,
    headers,
  });

  if (!response.ok) {
    throw new Error(await readError(response));
  }

  if (response.status === 204) return undefined as T;
  const text = await response.text();
  return (text ? JSON.parse(text) : undefined) as T;
}

function mapContactRow(row: ContactRow, countryMap: Map<string, Country>): Contact {
  const country = countryMap.get(row.country_id);
  if (!country) throw new Error("Un contacto tiene un país inválido.");
  return {
    id: row.id,
    countryId: row.country_id,
    country: country.name,
    countryCode: country.code,
    name: row.full_name,
    email: row.email ?? "",
    role: row.job_title ?? "",
    area: row.area,
    department: row.department ?? "",
    division: row.division ?? "",
    location: row.location ?? "",
    source: row.source ?? "manual",
    bambooEmployeeId: row.bamboo_employee_id ?? "",
    bambooEmployeeNumber: row.bamboo_employee_number ?? "",
    bambooStatus: row.bamboo_status ?? "",
    emailEnabled: row.email_enabled,
    googleChatEnabled: row.google_chat_enabled,
    calendarEnabled: row.calendar_enabled,
  };
}

function contactPayload(input: NewContact) {
  return {
    country_id: input.countryId,
    full_name: input.name.trim(),
    email: input.email.trim() || null,
    job_title: input.role.trim() || null,
    area: input.area,
    department: input.department?.trim() || null,
    division: input.division?.trim() || null,
    location: input.location?.trim() || null,
    source: input.source || "manual",
    bamboo_employee_id: input.bambooEmployeeId?.trim() || null,
    bamboo_employee_number: input.bambooEmployeeNumber?.trim() || null,
    bamboo_status: input.bambooStatus?.trim() || null,
    email_enabled: input.emailEnabled,
    google_chat_enabled: input.googleChatEnabled,
    calendar_enabled: input.calendarEnabled,
  };
}

function assertNotPastDate(value: string): void {
  const date = String(value ?? "").trim();
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);

  if (!match) {
    throw new Error("Selecciona una fecha válida.");
  }

  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);
  const parsed = new Date(Date.UTC(year, month - 1, day));

  if (
    parsed.getUTCFullYear() !== year ||
    parsed.getUTCMonth() !== month - 1 ||
    parsed.getUTCDate() !== day
  ) {
    throw new Error("Selecciona una fecha válida.");
  }

  const parts = new Intl.DateTimeFormat("en-US", {
    timeZone: "America/Santo_Domingo",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  }).formatToParts(new Date());
  const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
  const today = `${values.year}-${values.month}-${values.day}`;

  if (date < today) {
    throw new Error("No puedes crear ni mover una obligación a una fecha que ya pasó.");
  }
}

async function readError(response: Response): Promise<string> {
  try {
    const payload = (await response.json()) as { message?: string; details?: string };
    return payload.message ?? payload.details ?? "No se pudo guardar el cambio.";
  } catch {
    return "No se pudo guardar el cambio.";
  }
}

async function notifyGoogleCalendar(action: string, payload: Record<string, unknown>) {
  await postWebhook(googleCalendarWorkflowUrl, action, payload);
}

async function postWebhook(url: string, action: string, payload: Record<string, unknown>) {
  if (!url) return;
  try {
    await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "text/plain;charset=UTF-8" },
      body: JSON.stringify({ action, payload, source: "frontend" }),
      keepalive: true,
    });
  } catch {
    // La base de datos ya guardó el cambio. El webhook es una sincronización secundaria.
  }
}
