import {
  addDays,
  dayDiffInclusive,
  normalizeBookingOperationsPlan,
  type BookingOperationsPlan,
  type TimelineFilters,
  type TimelineResponse
} from "@rental/shared";

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

type TimelineItemRow = {
  id: string;
  category_id: string;
  category_name: string;
  name: string;
  short_name: string | null;
  use_short_name_on_timeline: boolean;
  serial_number: string | null;
  asset_tag: string | null;
  suggested_day_rate: number | null;
  status: "active" | "service" | "retired";
  notes: string | null;
  is_visible_on_timeline: boolean;
  created_at: Date;
  updated_at: Date;
};

type TimelineBookingRow = {
  id: string;
  equipment_item_id: string;
  relation_group_key: string;
  customer_name: string;
  order_number: string | null;
  project_number: string | null;
  project_name: string | null;
  notes: string | null;
  start_date: string;
  end_date: string;
  start_time: string | null;
  end_time: string | null;
  operations_plan: BookingOperationsPlan | null;
  day_rate: number | null;
  total_price: number | null;
  created_by: string | null;
  created_by_name: string | null;
  created_at: Date;
  updated_at: Date;
};

type TimelineFilterInput = Partial<TimelineFilters>;

const MODEL_SUFFIX_PATTERN = /\s+(?:[A-Z](?:\d{1,3})?|#\d{1,3})$/i;

const getTimelineGroupBaseName = (name: string) => name.replace(MODEL_SUFFIX_PATTERN, "").trim();

const getTimelineGroupKey = (categoryId: string, name: string) =>
  `${categoryId}::${getTimelineGroupBaseName(name).toLowerCase()}`;

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

const normalizeSearchText = (value: string | null | undefined) => value?.trim().toLowerCase() ?? "";

const itemMatchesSearch = (item: TimelineItemRow, searchTerm: string) => {
  if (!searchTerm) {
    return false;
  }

  return [item.name, item.short_name, item.serial_number, item.asset_tag].some((value) =>
    normalizeSearchText(value).includes(searchTerm)
  );
};

const timelineBookingMatchesSearch = (
  booking: TimelineResponse["groups"][number]["items"][number]["bookings"][number],
  searchTerm: string
) => {
  if (!searchTerm) {
    return false;
  }

  return [booking.customerName, booking.orderNumber, booking.projectNumber, booking.projectName].some((value) =>
    normalizeSearchText(value).includes(searchTerm)
  );
};

const bookingMatchesCustomerFilter = (booking: TimelineBookingRow, customerName: string) => {
  if (!customerName) {
    return true;
  }

  return normalizeSearchText(booking.customer_name).includes(customerName);
};

const normalizeFilters = (input: TimelineFilterInput): TimelineFilters => {
  const base = todayDate();
  const from = input.from && input.from.length === 10 ? input.from : addDays(base, -14);
  const to = input.to && input.to.length === 10 ? input.to : addDays(base, 35);

  return {
    from,
    to,
    categoryIds: input.categoryIds ?? [],
    status: input.status ?? "all",
    search: input.search?.trim() ?? "",
    customerName: input.customerName?.trim() ?? "",
    includeInactive: input.includeInactive ?? false
  };
};

export const getTimeline = async (input: TimelineFilterInput): Promise<TimelineResponse> => {
  const filters = normalizeFilters(input);
  const params: unknown[] = [];
  const conditions: string[] = ["i.is_visible_on_timeline = true"];

  if (filters.categoryIds.length > 0) {
    params.push(filters.categoryIds);
    conditions.push(`i.category_id = any($${params.length}::uuid[])`);
  }

  if (filters.status !== "all") {
    params.push(filters.status);
    conditions.push(`i.status = $${params.length}::equipment_status`);
  } else if (!filters.includeInactive) {
    conditions.push(`i.status = 'active'`);
  }

  const itemsResult = await query<TimelineItemRow>(
    `
      select
        i.id,
        i.category_id,
        c.name as category_name,
        i.name,
        i.short_name,
        i.use_short_name_on_timeline,
        i.serial_number,
        i.asset_tag,
        i.suggested_day_rate::double precision as suggested_day_rate,
        i.status,
        i.notes,
        i.is_visible_on_timeline,
        i.created_at,
        i.updated_at
      from equipment_items i
      join equipment_categories c on c.id = i.category_id
      where ${conditions.join(" and ")}
      order by
        case c.name
          when 'Laptopy' then 1
          when 'Tablety' then 2
          when 'Akcesoria' then 3
          else 99
        end,
        c.name asc,
        i.name asc
    `,
    params
  );

  const itemIds = itemsResult.rows.map((item) => item.id);

  const bookingsResult =
    itemIds.length === 0
      ? { rows: [] as TimelineBookingRow[] }
      : await query<TimelineBookingRow>(
          `
            select
              b.id,
              b.equipment_item_id,
              b.relation_group_key,
              b.customer_name,
              b.order_number,
              b.project_number,
              b.project_name,
              b.notes,
              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,
              b.day_rate::double precision as day_rate,
              b.total_price::double precision as total_price,
              b.created_by,
              u.name as created_by_name,
              b.created_at,
              b.updated_at
            from rental_bookings b
            left join users u on u.id = b.created_by
            where b.equipment_item_id = any($1::uuid[])
              and daterange(b.start_date, b.end_date, '[]') && daterange($2::date, $3::date, '[]')
            order by b.start_date asc
          `,
          [itemIds, filters.from, filters.to]
        );

  const bookingsByItem = new Map<string, TimelineResponse["groups"][number]["items"][number]["bookings"]>();

  for (const booking of bookingsResult.rows) {
    if (!bookingMatchesCustomerFilter(booking, filters.customerName)) {
      continue;
    }

    const list = bookingsByItem.get(booking.equipment_item_id) ?? [];
    list.push({
      id: booking.id,
      equipmentItemId: booking.equipment_item_id,
      relationGroupKey: booking.relation_group_key,
      customerName: booking.customer_name,
      orderNumber: booking.order_number,
      projectNumber: booking.project_number,
      projectName: booking.project_name,
      notes: booking.notes,
      startDate: booking.start_date,
      endDate: booking.end_date,
      startTime: booking.start_time,
      endTime: booking.end_time,
      operationsPlan: normalizeBookingOperationsPlan(booking.operations_plan, {
        startDate: booking.start_date,
        endDate: booking.end_date,
        startTime: booking.start_time,
        endTime: booking.end_time
      }),
      dayRate: booking.day_rate,
      totalPrice: booking.total_price,
      createdBy: booking.created_by,
      createdByName: booking.created_by_name,
      createdAt: booking.created_at.toISOString(),
      updatedAt: booking.updated_at.toISOString()
    });
    bookingsByItem.set(booking.equipment_item_id, list);
  }

  const groupsMap = new Map<string, TimelineResponse["groups"][number]>();

  for (const item of itemsResult.rows) {
    const itemSearchMatch = itemMatchesSearch(item, filters.search);
    const bookings =
      !filters.search || itemSearchMatch
        ? bookingsByItem.get(item.id) ?? []
        : (bookingsByItem.get(item.id) ?? []).filter((booking) => timelineBookingMatchesSearch(booking, filters.search));

    if (filters.customerName && bookings.length === 0) {
      continue;
    }

    if (filters.search && !itemSearchMatch && bookings.length === 0) {
      continue;
    }

    const groupKey = getTimelineGroupKey(item.category_id, item.name);
    const groupName = getTimelineGroupBaseName(item.name);
    const existingGroup = groupsMap.get(groupKey);

    const timelineItem = {
      id: item.id,
      categoryId: item.category_id,
      categoryName: item.category_name,
      name: item.name,
      shortName: item.short_name,
      useShortNameOnTimeline: item.use_short_name_on_timeline,
      serialNumber: item.serial_number,
      assetTag: item.asset_tag,
      suggestedDayRate: item.suggested_day_rate,
      status: item.status,
      notes: item.notes,
      isVisibleOnTimeline: item.is_visible_on_timeline,
      createdAt: item.created_at.toISOString(),
      updatedAt: item.updated_at.toISOString(),
      bookings
    };

    if (existingGroup) {
      existingGroup.items.push(timelineItem);
      existingGroup.totalItems += 1;
      existingGroup.visibleItems += 1;
      continue;
    }

    groupsMap.set(groupKey, {
      categoryId: groupKey,
      categoryName: groupName,
      totalItems: 1,
      visibleItems: 1,
      items: [timelineItem]
    });
  }

  return {
    range: {
      from: filters.from,
      to: filters.to,
      dayCount: dayDiffInclusive(filters.from, filters.to)
    },
    filters,
    groups: Array.from(groupsMap.values())
  };
};
