/**
 * Read-only dashboard widget queries for the Tenancies module (P7).
 *
 * Every function is port-scoped at the SQL level + assumes the module is
 * enabled by the caller. The four shapes mirror the dashboard widgets
 * documented in `docs/tenancies-design.md` § "Reporting widgets":
 *
 *   1. Occupancy heatmap by month
 *   2. Renewals at risk (next 90 days)
 *   3. Revenue forecast by tenure expiry
 *   4. Tenancy by tenure type breakdown
 */

import { and, count, eq, gte, lte, sql } from 'drizzle-orm';

import { db } from '@/lib/db';
import { berths } from '@/lib/db/schema/berths';
import { clients } from '@/lib/db/schema/clients';
import { berthTenancies } from '@/lib/db/schema/tenancies';
import { yachts } from '@/lib/db/schema/yachts';

const DAY_MS = 86_400_000;

// ─── 1. Occupancy heatmap by month ───────────────────────────────────────────

export interface OccupancyHeatmapCell {
  area: string;
  month: string; // ISO YYYY-MM
  occupancyPct: number; // 0..100
  berthCount: number;
  occupiedBerthMonths: number;
}

/**
 * Returns one cell per (area, month) pair across the requested range.
 * Occupancy = fraction of the area's berths covered by an active /
 * ended tenancy whose date range overlaps the month.
 */
export async function getOccupancyHeatmap(
  portId: string,
  range: { from: Date; to: Date },
): Promise<OccupancyHeatmapCell[]> {
  const rows = await db.execute<{
    area: string;
    month: string;
    berth_count: string;
    occupied_count: string;
  }>(sql`
    WITH months AS (
      SELECT to_char(generate_series(
        date_trunc('month', ${range.from.toISOString()}::timestamptz),
        date_trunc('month', ${range.to.toISOString()}::timestamptz),
        '1 month'
      ), 'YYYY-MM') AS month
    ),
    areas AS (
      SELECT DISTINCT COALESCE(area, '—') AS area
      FROM berths
      WHERE port_id = ${portId} AND archived_at IS NULL
    ),
    grid AS (
      SELECT a.area, m.month FROM areas a CROSS JOIN months m
    ),
    berth_per_area AS (
      SELECT COALESCE(area, '—') AS area, COUNT(*) AS berth_count
      FROM berths
      WHERE port_id = ${portId} AND archived_at IS NULL
      GROUP BY area
    ),
    occupied AS (
      SELECT
        COALESCE(b.area, '—') AS area,
        to_char(date_trunc('month', m.month::date), 'YYYY-MM') AS month,
        COUNT(DISTINCT bt.berth_id) AS occupied_count
      FROM berth_tenancies bt
      INNER JOIN berths b ON b.id = bt.berth_id
      CROSS JOIN months m
      WHERE bt.port_id = ${portId}
        AND bt.status IN ('active', 'ended')
        AND bt.start_date <= (date_trunc('month', m.month::date) + interval '1 month' - interval '1 day')
        AND (bt.end_date IS NULL OR bt.end_date >= date_trunc('month', m.month::date))
      GROUP BY b.area, month
    )
    SELECT g.area, g.month,
           COALESCE(bpa.berth_count, 0)::text AS berth_count,
           COALESCE(o.occupied_count, 0)::text AS occupied_count
    FROM grid g
    LEFT JOIN berth_per_area bpa ON bpa.area = g.area
    LEFT JOIN occupied o ON o.area = g.area AND o.month = g.month
    ORDER BY g.area, g.month
  `);

  return rows.map((r) => {
    const berthCount = Number(r.berth_count) || 0;
    const occupied = Number(r.occupied_count) || 0;
    return {
      area: r.area,
      month: r.month,
      berthCount,
      occupiedBerthMonths: occupied,
      occupancyPct: berthCount > 0 ? Math.round((occupied / berthCount) * 100) : 0,
    };
  });
}

// ─── 2. Renewals at risk ─────────────────────────────────────────────────────

export interface RenewalAtRisk {
  tenancyId: string;
  berthId: string;
  mooringNumber: string;
  clientId: string;
  clientName: string;
  yachtId: string;
  yachtName: string | null;
  endDate: string; // ISO date
  daysUntilEnd: number;
  tenureType: string;
}

export async function getRenewalsAtRisk(
  portId: string,
  options: { windowDays?: number } = {},
): Promise<RenewalAtRisk[]> {
  const windowDays = options.windowDays ?? 90;
  const now = new Date();
  const horizon = new Date(now.getTime() + windowDays * DAY_MS);

  const rows = await db
    .select({
      id: berthTenancies.id,
      berthId: berthTenancies.berthId,
      mooringNumber: berths.mooringNumber,
      clientId: berthTenancies.clientId,
      clientName: clients.fullName,
      yachtId: berthTenancies.yachtId,
      yachtName: yachts.name,
      endDate: berthTenancies.endDate,
      tenureType: berthTenancies.tenureType,
    })
    .from(berthTenancies)
    .innerJoin(berths, eq(berths.id, berthTenancies.berthId))
    .innerJoin(clients, eq(clients.id, berthTenancies.clientId))
    .innerJoin(yachts, eq(yachts.id, berthTenancies.yachtId))
    .where(
      and(
        eq(berthTenancies.portId, portId),
        eq(berthTenancies.status, 'active'),
        sql`${berthTenancies.endDate} IS NOT NULL`,
        lte(berthTenancies.endDate, horizon),
        gte(berthTenancies.endDate, now),
        // No successor tenancy started before the end date for this berth.
        sql`NOT EXISTS (
          SELECT 1 FROM berth_tenancies succ
          WHERE succ.berth_id = ${berthTenancies.berthId}
            AND succ.port_id = ${portId}
            AND succ.id <> ${berthTenancies.id}
            AND succ.status IN ('pending', 'active')
            AND succ.start_date >= ${berthTenancies.startDate}
        )`,
      ),
    )
    .orderBy(berthTenancies.endDate);

  return rows.map((r) => ({
    tenancyId: r.id,
    berthId: r.berthId,
    mooringNumber: r.mooringNumber,
    clientId: r.clientId,
    clientName: r.clientName,
    yachtId: r.yachtId,
    yachtName: r.yachtName,
    endDate: r.endDate!.toISOString(),
    daysUntilEnd: Math.max(0, Math.floor((r.endDate!.getTime() - now.getTime()) / DAY_MS)),
    tenureType: r.tenureType,
  }));
}

// ─── 3. Revenue forecast by tenure expiry ────────────────────────────────────

export interface RevenueForecastBucket {
  quarterEnd: string; // ISO YYYY-MM-DD
  endingTenancyCount: number;
  totalAtRisk: number;
  currency: string | null;
}

export async function getRevenueForecast(
  portId: string,
  options: { horizonQuarters?: number } = {},
): Promise<RevenueForecastBucket[]> {
  const horizonQuarters = options.horizonQuarters ?? 8;
  const now = new Date();
  const horizon = new Date(now.getFullYear(), now.getMonth() + horizonQuarters * 3, 0);

  const rows = await db.execute<{
    quarter_end: string;
    ending_count: string;
    total_at_risk: string;
    currency: string | null;
  }>(sql`
    SELECT
      to_char(date_trunc('quarter', bt.end_date) + interval '3 months' - interval '1 day', 'YYYY-MM-DD') AS quarter_end,
      COUNT(*)::text AS ending_count,
      COALESCE(SUM(b.price::numeric), 0)::text AS total_at_risk,
      MAX(b.price_currency) AS currency
    FROM berth_tenancies bt
    INNER JOIN berths b ON b.id = bt.berth_id
    WHERE bt.port_id = ${portId}
      AND bt.status = 'active'
      AND bt.end_date IS NOT NULL
      AND bt.end_date >= ${now.toISOString()}
      AND bt.end_date <= ${horizon.toISOString()}
    GROUP BY quarter_end
    ORDER BY quarter_end
  `);

  return rows.map((r) => ({
    quarterEnd: r.quarter_end,
    endingTenancyCount: Number(r.ending_count) || 0,
    totalAtRisk: Number(r.total_at_risk) || 0,
    currency: r.currency,
  }));
}

// ─── 4. Tenancy by tenure type breakdown ─────────────────────────────────────

export interface TenureBreakdownRow {
  tenureType: string;
  activeCount: number;
}

export async function getTenureTypeBreakdown(portId: string): Promise<TenureBreakdownRow[]> {
  const rows = await db
    .select({
      tenureType: berthTenancies.tenureType,
      activeCount: count(),
    })
    .from(berthTenancies)
    .where(and(eq(berthTenancies.portId, portId), eq(berthTenancies.status, 'active')))
    .groupBy(berthTenancies.tenureType);

  return rows.map((r) => ({
    tenureType: r.tenureType,
    activeCount: Number(r.activeCount),
  }));
}
