import {
  BOOKING_OPERATION_TASK_TYPES,
  addDays,
  normalizeBookingOperationsPlan,
  normalizeBookingTime,
  type BookingOperationTaskType,
  type BookingOperationsPlan,
  type OperationsEquipmentKind,
  type OperationsResponse,
  type OperationsTaskProgressPayload,
  type UserRole
} from "@rental/shared";

import { query } from "../../db/pool.js";

type OperationsBookingRow = {
  id: string;
  equipment_item_id: string;
  customer_name: string;
  order_number: string | null;
  project_number: string | null;
  project_name: string | null;
  start_date: string;
  end_date: string;
  start_time: string | null;
  end_time: string | null;
  operations_plan: BookingOperationsPlan | null;
  item_name: string;
  serial_number: string | null;
  asset_tag: string | null;
  category_name: string;
};

type OperationsProgressRow = {
  task_type: BookingOperationTaskType;
  task_date: string;
  task_time: string;
  scope_key: string;
  checked_kinds: OperationsEquipmentKind[] | null;
  confirmed_item_ids: string[] | null;
  finished_at: string | null;
};

type OperationsQuery = {
  from?: string;
  to?: string;
};

const todayDate = () => new Date().toISOString().slice(0, 10);

const normalizeRange = (input: OperationsQuery) => {
  const today = todayDate();
  return {
    from: input.from && input.from.length === 10 ? input.from : today,
    to: input.to && input.to.length === 10 ? input.to : addDays(today, 6)
  };
};

const buildScopeKey = (booking: {
  id: string;
  order_number: string | null;
  project_number: string | null;
}) => booking.project_number?.trim() || booking.order_number?.trim() || booking.id;

const normalizeTaskTimeKey = (value: string | null | undefined) => normalizeBookingTime(value) ?? "";

const buildTaskId = (input: {
  type: BookingOperationTaskType;
  date: string;
  time: string | null | undefined;
  scopeKey: string;
}) => [input.type, input.date, normalizeTaskTimeKey(input.time), input.scopeKey].join("::");

const isEquipmentKind = (value: unknown): value is OperationsEquipmentKind =>
  value === "laptop" || value === "tablet" || value === "printer" || value === "router" || value === "flipchart";

const sanitizeCheckedKinds = (value: unknown) =>
  Array.from(new Set((Array.isArray(value) ? value : []).filter(isEquipmentKind)));

const sanitizeConfirmedItemIds = (value: unknown) =>
  Array.from(new Set((Array.isArray(value) ? value : []).filter((entry): entry is string => typeof entry === "string" && entry.length > 0)));

const detectKindsFromSource = (source: string) => {
  const normalized = source.toLocaleLowerCase("pl");
  const kinds = new Set<OperationsEquipmentKind>();

  if (/(laptop|laptopy|notebook|macbook|legion|probook|elitebook|latitude|thinkpad|hewlett packard|hp\b|dell|lenovo|\bg\d{1,2}\b)/.test(normalized)) {
    kinds.add("laptop");
  }

  if (/(tablet|ipad|galaxy tab|tab\b|surface go)/.test(normalized)) {
    kinds.add("tablet");
  }

  if (/(drukark|printer)/.test(normalized)) {
    kinds.add("printer");
  }

  if (/router/.test(normalized)) {
    kinds.add("router");
  }

  if (/flipchart/.test(normalized)) {
    kinds.add("flipchart");
  }

  return Array.from(kinds);
};

const detectEquipmentKinds = (booking: Pick<OperationsBookingRow, "item_name" | "category_name">): OperationsEquipmentKind[] => {
  const itemName = booking.item_name.toLocaleLowerCase("pl");
  const categoryName = booking.category_name.toLocaleLowerCase("pl");
  const itemKinds = detectKindsFromSource(itemName);
  const categoryKinds = detectKindsFromSource(categoryName);
  const hasUnsupportedAccessoryMarker =
    /(monitor|spotlight|logitech|akcesori|akcesoria|mysz|klawiatur|hub|dock|stacja dokuj|adapter|zasilacz|kabel)/.test(`${itemName} ${categoryName}`);

  if (itemKinds.length > 0) {
    return itemKinds;
  }

  if (categoryKinds.length > 0) {
    return categoryKinds;
  }

  if (!hasUnsupportedAccessoryMarker) {
    return ["laptop"];
  }

  return [];
};

const detectPrimaryEquipmentKind = (booking: Pick<OperationsBookingRow, "item_name" | "category_name">) =>
  detectEquipmentKinds(booking)[0] ?? null;

const fetchOperationsBookings = async (range: { from: string; to: string }) =>
  query<OperationsBookingRow>(
    `
      select
        b.id,
        b.equipment_item_id,
        b.customer_name,
        b.order_number,
        b.project_number,
        b.project_name,
        b.start_date::text,
        b.end_date::text,
        to_char(b.start_time, 'HH24:MI') as start_time,
        to_char(b.end_time, 'HH24:MI') as end_time,
        b.operations_plan,
        i.name as item_name,
        i.serial_number,
        i.asset_tag,
        c.name as category_name
      from rental_bookings b
      join equipment_items i on i.id = b.equipment_item_id
      join equipment_categories c on c.id = i.category_id
      where
        coalesce(b.operations_plan->'preparation'->>'date', b.start_date::text) between $1 and $2
        or coalesce(b.operations_plan->'delivery'->>'date', b.start_date::text) between $1 and $2
        or coalesce(b.operations_plan->'pickup'->>'date', b.end_date::text) between $1 and $2
        or coalesce(b.operations_plan->'verification'->>'date', b.end_date::text) between $1 and $2
      order by b.start_date asc, b.created_at asc
    `,
    [range.from, range.to]
  );

const fetchProgressRows = async (range: { from: string; to: string }) =>
  query<OperationsProgressRow>(
    `
      select
        task_type,
        task_date::text,
        task_time,
        scope_key,
        checked_kinds,
        confirmed_item_ids,
        finished_at::text
      from operations_task_progress
      where task_date between $1 and $2
    `,
    [range.from, range.to]
  );

const buildProgressMap = (rows: OperationsProgressRow[]) =>
  new Map(
    rows.map((row) => [
      buildTaskId({
        type: row.task_type,
        date: row.task_date,
        time: row.task_time || null,
        scopeKey: row.scope_key
      }),
      {
        checkedEquipmentKinds: sanitizeCheckedKinds(row.checked_kinds),
        confirmedEquipmentItemIds: sanitizeConfirmedItemIds(row.confirmed_item_ids),
        isFinished: Boolean(row.finished_at),
        finishedAt: row.finished_at
      }
    ])
  );

export const getOperations = async (_role: UserRole, input: OperationsQuery): Promise<OperationsResponse> => {
  const range = normalizeRange(input);
  const [bookingsResult, progressResult] = await Promise.all([fetchOperationsBookings(range), fetchProgressRows(range)]);
  const progressMap = buildProgressMap(progressResult.rows);
  const taskMap = new Map<string, OperationsResponse["tasks"][number]>();

  for (const booking of bookingsResult.rows) {
    const equipmentKinds = detectEquipmentKinds(booking);
    const primaryEquipmentKind = detectPrimaryEquipmentKind(booking);
    const scopeKey = buildScopeKey(booking);
    const operationsPlan = normalizeBookingOperationsPlan(booking.operations_plan, {
      startDate: booking.start_date,
      endDate: booking.end_date,
      startTime: booking.start_time,
      endTime: booking.end_time
    });

    for (const task of BOOKING_OPERATION_TASK_TYPES) {
      const scheduled = operationsPlan[task.value];

      if (!scheduled.date || scheduled.date < range.from || scheduled.date > range.to) {
        continue;
      }

      const taskId = buildTaskId({
        type: task.value,
        date: scheduled.date,
        time: scheduled.time,
        scopeKey
      });
      const existing = taskMap.get(taskId);
      const taskItem: OperationsResponse["tasks"][number]["taskItems"][number] = {
        bookingId: booking.id,
        equipmentItemId: booking.equipment_item_id,
        name: booking.item_name,
        kind: primaryEquipmentKind,
        serialNumber: booking.serial_number,
        assetTag: booking.asset_tag
      };

      if (existing) {
        existing.bookingIds = Array.from(new Set([...existing.bookingIds, booking.id]));
        existing.itemNames = Array.from(new Set([...existing.itemNames, booking.item_name]));
        existing.taskItems = existing.taskItems.some((item) => item.equipmentItemId === booking.equipment_item_id)
          ? existing.taskItems
          : [...existing.taskItems, taskItem];
        existing.equipmentKinds = Array.from(new Set([...existing.equipmentKinds, ...equipmentKinds]));
        existing.itemCount = existing.bookingIds.length;
        continue;
      }

      const progress = progressMap.get(taskId);

      taskMap.set(taskId, {
        id: taskId,
        scopeKey,
        type: task.value,
        date: scheduled.date,
        time: scheduled.time ?? null,
        customerName: booking.customer_name,
        orderNumber: booking.order_number,
        projectNumber: booking.project_number,
        projectName: booking.project_name,
        bookingIds: [booking.id],
        itemNames: [booking.item_name],
        itemCount: 1,
        taskItems: [taskItem],
        equipmentKinds,
        checkedEquipmentKinds: progress?.checkedEquipmentKinds ?? [],
        confirmedEquipmentItemIds: progress?.confirmedEquipmentItemIds ?? [],
        isFinished: progress?.isFinished ?? false,
        finishedAt: progress?.finishedAt ?? null
      });
    }
  }

  for (const task of taskMap.values()) {
    task.checkedEquipmentKinds = task.checkedEquipmentKinds.filter((kind) => task.equipmentKinds.includes(kind));
    task.confirmedEquipmentItemIds = task.confirmedEquipmentItemIds.filter((itemId) =>
      task.taskItems.some((taskItem) => taskItem.equipmentItemId === itemId)
    );

    if (task.equipmentKinds.length === 0) {
      task.isFinished = false;
      task.finishedAt = null;
    }
  }

  return {
    from: range.from,
    to: range.to,
    tasks: Array.from(taskMap.values()).sort(
      (left, right) =>
        left.date.localeCompare(right.date) ||
        (left.time ?? "99:99").localeCompare(right.time ?? "99:99") ||
        left.type.localeCompare(right.type) ||
        left.customerName.localeCompare(right.customerName, "pl")
    )
  };
};

export const updateOperationsTaskProgress = async (
  userId: string,
  payload: OperationsTaskProgressPayload
): Promise<{
  checkedEquipmentKinds: OperationsEquipmentKind[];
  confirmedEquipmentItemIds: string[];
  isFinished: boolean;
  finishedAt: string | null;
}> => {
  const checkedKinds = sanitizeCheckedKinds(payload.checkedEquipmentKinds);
  const confirmedItemIds = sanitizeConfirmedItemIds(payload.confirmedEquipmentItemIds);
  const finishedAt = payload.markFinished ? new Date().toISOString() : null;
  const result = await query<{
    checked_kinds: OperationsEquipmentKind[] | null;
    confirmed_item_ids: string[] | null;
    finished_at: string | null;
  }>(
    `
      insert into operations_task_progress (
        task_type,
        task_date,
        task_time,
        scope_key,
        checked_kinds,
        confirmed_item_ids,
        finished_at,
        finished_by,
        updated_at
      )
      values ($1, $2::date, $3, $4, $5::jsonb, $6::jsonb, $7::timestamptz, $8::uuid, now())
      on conflict (task_type, task_date, task_time, scope_key)
      do update set
        checked_kinds = excluded.checked_kinds,
        confirmed_item_ids = excluded.confirmed_item_ids,
        finished_at = excluded.finished_at,
        finished_by = case when excluded.finished_at is null then null else excluded.finished_by end,
        updated_at = now()
      returning checked_kinds, confirmed_item_ids, finished_at::text
    `,
    [
      payload.type,
      payload.date,
      normalizeTaskTimeKey(payload.time),
      payload.scopeKey.trim(),
      JSON.stringify(checkedKinds),
      JSON.stringify(confirmedItemIds),
      finishedAt,
      finishedAt ? userId : null
    ]
  );

  return {
    checkedEquipmentKinds: sanitizeCheckedKinds(result.rows[0]?.checked_kinds),
    confirmedEquipmentItemIds: sanitizeConfirmedItemIds(result.rows[0]?.confirmed_item_ids),
    isFinished: Boolean(result.rows[0]?.finished_at),
    finishedAt: result.rows[0]?.finished_at ?? null
  };
};
