import { addDays, dayDiffInclusive, 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;
  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;
  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;
  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 todayDate = () => new Date().toISOString().slice(0, 10);

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'`);
  }

  if (filters.search) {
    params.push(`%${filters.search.toLowerCase()}%`);
    conditions.push(`
      (
        lower(i.name) like $${params.length}
        or lower(coalesce(i.serial_number, '')) like $${params.length}
        or lower(coalesce(i.asset_tag, '')) like $${params.length}
      )
    `);
  }

  const itemsResult = await query<TimelineItemRow>(
    `
      select
        i.id,
        i.category_id,
        c.name as category_name,
        i.name,
        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.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.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, '[]')
              ${filters.customerName ? "and lower(b.customer_name) like $4" : ""}
            order by b.start_date asc
          `,
          filters.customerName ? [itemIds, filters.from, filters.to, `%${filters.customerName.toLowerCase()}%`] : [itemIds, filters.from, filters.to]
        );

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

  for (const booking of bookingsResult.rows) {
    const list = bookingsByItem.get(booking.equipment_item_id) ?? [];
    list.push({
      id: booking.id,
      equipmentItemId: booking.equipment_item_id,
      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,
      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 bookings = bookingsByItem.get(item.id) ?? [];

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

    const existingGroup = groupsMap.get(item.category_id);

    const timelineItem = {
      id: item.id,
      categoryId: item.category_id,
      categoryName: item.category_name,
      name: item.name,
      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(item.category_id, {
      categoryId: item.category_id,
      categoryName: item.category_name,
      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())
  };
};
