/**
 * Server-side payload builders for the standalone Sales + Operational
 * reports. The interactive Export button builds the same payload in the
 * browser via the report client's local state — but scheduled runs
 * execute in a worker context with no browser state, so we replicate
 * the same shape from saved-template configs here.
 *
 * Output is a `ReportPayload` ready to feed `PayloadReportDocument`
 * (PDF) or any other format-agnostic exporter.
 */

import { STAGE_LABELS, OUTCOME_LABELS, type PipelineStage } from '@/lib/constants';
import { rangeToBounds, type DateRange } from '@/lib/analytics/range';
import { formatMoney } from '@/lib/reports/format-currency';
import type { ReportPayload } from '@/lib/reports/types';
import {
  getSalesKpis,
  getPipelineFunnel,
  getStageVelocity,
  getWinRateOverTime,
  getSourceConversion,
  getRepLeaderboard,
  getDealHeat,
  getRepPerformanceDetail,
  getStalledDeals,
  getClosingThisMonth,
  getRecentWins,
  getLostReasonBreakdown,
  type SalesFilters,
} from '@/lib/services/reports/sales.service';
import {
  getOperationalKpis,
  getOccupancyByArea,
  getTenanciesEndingSoon,
  getVacantBerths,
  getStuckSigning,
  getHighestValueVacant,
} from '@/lib/services/reports/operational.service';

/** Shape of a stored template `config` for the Sales report. */
interface SalesTemplateConfig {
  kind: 'sales';
  range?: DateRange;
  filters?: {
    stage?: string[];
    leadCategory?: string[];
    outcome?: string[];
  };
}

/** Shape of a stored template `config` for the Operational report. */
interface OperationalTemplateConfig {
  kind: 'operational';
  range?: DateRange;
  statusMixMode?: 'absolute' | 'proportional';
}

export async function buildSalesReportPayload(
  portId: string,
  config: SalesTemplateConfig,
): Promise<ReportPayload> {
  const range = config.range ?? '30d';
  const bounds = rangeToBounds(range);

  const filters: SalesFilters | undefined = config.filters
    ? {
        stages: config.filters.stage as PipelineStage[] | undefined,
        leadCategories: config.filters.leadCategory,
        outcomes: config.filters.outcome,
      }
    : undefined;

  const [
    kpis,
    funnel,
    stageVelocity,
    winRateOverTime,
    sourceConversion,
    repLeaderboard,
    dealHeat,
    stalledDeals,
    closingThisMonth,
    recentWins,
    lostReasonBreakdown,
  ] = await Promise.all([
    getSalesKpis(portId, bounds),
    getPipelineFunnel(portId),
    getStageVelocity(portId),
    getWinRateOverTime(portId, bounds),
    getSourceConversion(portId),
    getRepLeaderboard(portId, bounds),
    getDealHeat(portId),
    getStalledDeals(portId, filters),
    getClosingThisMonth(portId, filters),
    getRecentWins(portId, filters),
    getLostReasonBreakdown(portId, bounds, filters),
  ]);
  // RepPerformanceDetail is unused in the scheduled-output payload —
  // the leaderboard table covers the same ground; adding it on a PDF
  // page just duplicates the data.
  void getRepPerformanceDetail;

  // All money values returned by the sales service are already in the
  // port's reporting currency (service converts on read). Money rows
  // are pre-formatted into strings below so the column emits a ready-
  // to-render value regardless of whether the downstream renderer keeps
  // the column.format callback (XLSX / on-page CSV) or drops it (server
  // PDF over a JSON boundary).
  const portCurrency = kpis.pipelineValueCurrency;
  const fmtAmount = (v: number | null | undefined): string =>
    v === null || v === undefined ? '—' : formatMoney(v, portCurrency);

  return {
    title: 'Sales performance',
    description: 'Rep performance, win rates, pipeline value, stalled deals, deal heat.',
    filenameSlug: 'sales-performance',
    range: bounds,
    kpis: [
      { label: 'Active interests', value: kpis.activeInterests },
      { label: 'Won in period', value: kpis.wonInWindow },
      {
        label: 'Lost in period',
        value: kpis.lostInWindow,
        hint: kpis.lossBreakdown
          .map((b) => `${b.count} ${b.outcome.replace(/^lost_/, '')}`)
          .join(', '),
      },
      {
        label: 'Win rate',
        value: kpis.winRate === null ? '—' : `${(kpis.winRate * 100).toFixed(1)}%`,
      },
      {
        label: 'Pipeline value',
        value: formatMoney(kpis.pipelineValue, kpis.pipelineValueCurrency),
        hint: `${kpis.pipelineValueTotalActiveCount} active interests`,
      },
      {
        label: 'Avg time to close',
        value:
          kpis.medianTimeToCloseDays === null
            ? '—'
            : `${kpis.medianTimeToCloseDays.toFixed(1)} days`,
        hint:
          kpis.medianTimeToCloseDays !== null
            ? `based on ${kpis.timeToCloseSampleSize} won deals`
            : 'need ≥3 won deals',
      },
      {
        label: 'New leads',
        value: kpis.newLeadsInWindow,
        hint: kpis.newLeadsBySource.map((s) => `${s.count} ${s.source}`).join(', '),
      },
    ],
    sections: [
      {
        title: 'Pipeline funnel',
        columns: [
          { key: 'stage', label: 'Stage' },
          { key: 'count', label: 'Active deals', align: 'right' },
          {
            key: 'dropoffFromPrior',
            label: 'Drop-off vs prior',
            align: 'right',
            format: (v) =>
              v === null || v === undefined ? '' : `${((v as number) * 100).toFixed(0)}%`,
          },
        ],
        rows: funnel.map((r) => ({
          stage: STAGE_LABELS[r.stage],
          count: r.count,
          dropoffFromPrior: r.dropoffFromPrior,
        })),
      },
      {
        title: 'Stage velocity',
        columns: [
          { key: 'stage', label: 'Stage' },
          {
            key: 'medianDays',
            label: 'Median days in stage',
            align: 'right',
            format: (v) => (v === null || v === undefined ? '—' : (v as number).toFixed(1)),
          },
          {
            key: 'p90Days',
            label: 'p90 days',
            align: 'right',
            format: (v) => (v === null || v === undefined ? '—' : (v as number).toFixed(1)),
          },
          { key: 'transitions', label: 'Sample size', align: 'right' },
        ],
        rows: stageVelocity.map((r) => ({
          stage: STAGE_LABELS[r.stage],
          medianDays: r.medianDays,
          p90Days: r.p90Days,
          transitions: r.transitions,
        })),
      },
      {
        title: `Win rate over time (${winRateOverTime.granularity})`,
        columns: [
          { key: 'bucket', label: 'Period' },
          { key: 'won', label: 'Won', align: 'right' },
          { key: 'lost', label: 'Lost', align: 'right' },
          {
            key: 'winRate',
            label: 'Win rate',
            align: 'right',
            format: (v) =>
              v === null || v === undefined ? '—' : `${((v as number) * 100).toFixed(1)}%`,
          },
        ],
        rows: winRateOverTime.points.map((p) => ({ ...p })),
      },
      {
        title: 'Source → win conversion',
        columns: [
          { key: 'source', label: 'Source' },
          { key: 'won', label: 'Won', align: 'right' },
          { key: 'lost', label: 'Lost', align: 'right' },
          { key: 'cancelled', label: 'Cancelled', align: 'right' },
          { key: 'in_flight', label: 'In flight', align: 'right' },
          { key: 'total', label: 'Total', align: 'right' },
        ],
        rows: sourceConversion.map((r) => ({
          source: r.source,
          won: r.counts.won,
          lost: r.counts.lost,
          cancelled: r.counts.cancelled,
          in_flight: r.counts.in_flight,
          total: r.total,
        })),
      },
      {
        title: 'Rep leaderboard',
        columns: [
          { key: 'displayName', label: 'Rep' },
          { key: 'newDeals', label: 'New', align: 'right' },
          { key: 'won', label: 'Won', align: 'right' },
          { key: 'lost', label: 'Lost', align: 'right' },
          { key: 'inFlight', label: 'In flight', align: 'right' },
          { key: 'pipelineValue', label: 'Pipeline value', align: 'right' },
          {
            key: 'winRate',
            label: 'Win rate',
            align: 'right',
            format: (v) =>
              v === null || v === undefined ? '' : `${((v as number) * 100).toFixed(0)}%`,
          },
        ],
        rows: repLeaderboard.map((r) => ({
          ...r,
          pipelineValue: formatMoney(r.pipelineValue, r.pipelineValueCurrency),
        })),
      },
      {
        title: 'Deal heat — hottest deals',
        columns: [
          { key: 'clientName', label: 'Client' },
          { key: 'mooringNumber', label: 'Berth' },
          {
            key: 'stage',
            label: 'Stage',
            format: (v) => STAGE_LABELS[v as PipelineStage] ?? '',
          },
          { key: 'bucket', label: 'Heat' },
          {
            key: 'daysSinceLastContact',
            label: 'Days since contact',
            align: 'right',
            format: (v) => (v === null || v === undefined ? 'never' : String(v)),
          },
          { key: 'pipelineValue', label: 'Value', align: 'right' },
        ],
        rows: dealHeat.topDeals.map((d) => ({
          ...d,
          pipelineValue: formatMoney(d.pipelineValue, d.pipelineValueCurrency),
        })),
      },
      {
        title: 'Stalled deals',
        columns: [
          { key: 'clientName', label: 'Client' },
          { key: 'primaryBerth', label: 'Berth' },
          { key: 'stage', label: 'Stage', format: (v) => STAGE_LABELS[v as PipelineStage] ?? '' },
          { key: 'rep', label: 'Rep' },
          { key: 'daysSinceLastContact', label: 'Days since contact', align: 'right' },
          { key: 'stageValue', label: 'Value', align: 'right' },
        ],
        rows: stalledDeals.map((r) => ({
          ...r,
          stageValue: fmtAmount(r.stageValue),
        })),
      },
      {
        title: 'Closing this month',
        columns: [
          { key: 'clientName', label: 'Client' },
          { key: 'primaryBerth', label: 'Berth' },
          { key: 'stage', label: 'Stage', format: (v) => STAGE_LABELS[v as PipelineStage] ?? '' },
          { key: 'rep', label: 'Rep' },
          { key: 'daysInStage', label: 'Days in stage', align: 'right' },
          { key: 'stageValue', label: 'Value', align: 'right' },
        ],
        rows: closingThisMonth.map((r) => ({
          ...r,
          stageValue: fmtAmount(r.stageValue),
        })),
      },
      {
        title: 'Recent wins',
        columns: [
          { key: 'clientName', label: 'Client' },
          { key: 'primaryBerth', label: 'Berth' },
          { key: 'rep', label: 'Rep' },
          { key: 'outcomeAt', label: 'Closed at', format: (v) => String(v).slice(0, 10) },
          { key: 'finalValue', label: 'Value', align: 'right' },
          { key: 'daysToClose', label: 'Days to close', align: 'right' },
        ],
        rows: recentWins.map((r) => ({
          ...r,
          finalValue: formatMoney(r.finalValue, r.currency),
        })),
      },
      {
        title: 'Lost-reason breakdown',
        columns: [
          {
            key: 'outcome',
            label: 'Outcome',
            format: (v) => OUTCOME_LABELS[v as string] ?? String(v),
          },
          { key: 'count', label: 'Count', align: 'right' },
          { key: 'totalValueLost', label: 'Value lost', align: 'right' },
          {
            key: 'avgDaysFromFirstContactToLoss',
            label: 'Avg days to loss',
            align: 'right',
            format: (v) => (v === null || v === undefined ? '—' : (v as number).toFixed(1)),
          },
        ],
        rows: lostReasonBreakdown.map((r) => ({
          ...r,
          totalValueLost: formatMoney(r.totalValueLost, r.currency),
        })),
      },
    ],
  };
}

export async function buildOperationalReportPayload(
  portId: string,
  config: OperationalTemplateConfig,
): Promise<ReportPayload> {
  const range = config.range ?? '30d';
  const bounds = rangeToBounds(range);

  const [kpis, occupancyByArea, endingSoon, vacantBerths, stuckSigning, highestValueVacant] =
    await Promise.all([
      getOperationalKpis(portId, bounds),
      getOccupancyByArea(portId),
      getTenanciesEndingSoon(portId),
      getVacantBerths(portId),
      getStuckSigning(portId),
      getHighestValueVacant(portId),
    ]);

  const tenanciesOn = kpis.tenanciesModuleEnabled;

  return {
    title: 'Operational',
    description:
      'Berth utilisation, tenancy lifecycle, signing turnaround, and operational bottlenecks.',
    filenameSlug: 'operational',
    range: bounds,
    kpis: [
      { label: 'Total berths', value: kpis.totalBerths },
      { label: 'Sold %', value: `${kpis.soldPct.toFixed(1)}%` },
      { label: 'Under offer %', value: `${kpis.underOfferPct.toFixed(1)}%` },
      {
        label: 'Active tenancies',
        value: kpis.activeTenancies ?? '—',
        hint: tenanciesOn ? undefined : 'Tenancies module disabled',
      },
      {
        label: 'Avg tenancy length',
        value:
          kpis.avgTenancyLengthYears !== null
            ? `${kpis.avgTenancyLengthYears.toFixed(1)} years`
            : '—',
      },
      { label: 'Berths in conflict', value: kpis.berthsInConflict },
    ],
    sections: [
      {
        title: 'Occupancy by area',
        columns: [
          { key: 'area', label: 'Area' },
          { key: 'available', label: 'Available', align: 'right' },
          { key: 'underOffer', label: 'Under offer', align: 'right' },
          { key: 'sold', label: 'Sold', align: 'right' },
          { key: 'total', label: 'Total', align: 'right' },
        ],
        rows: occupancyByArea.map((r) => ({ ...r })),
      },
      {
        title: 'Tenancies ending soon (next 6 months)',
        columns: [
          { key: 'clientName', label: 'Client' },
          { key: 'primaryBerth', label: 'Berth' },
          { key: 'tenureType', label: 'Tenure type' },
          { key: 'endDate', label: 'End date', format: (v) => String(v).slice(0, 10) },
          { key: 'daysUntilEnd', label: 'Days until end', align: 'right' },
        ],
        rows: endingSoon.map((r) => ({ ...r })),
      },
      {
        title: 'Vacant berths (>60 days)',
        columns: [
          { key: 'mooring', label: 'Mooring' },
          { key: 'area', label: 'Area' },
          { key: 'dimensions', label: 'Dimensions' },
          { key: 'price', label: 'Price', align: 'right' },
          { key: 'daysAvailable', label: 'Days available', align: 'right' },
        ],
        // Pre-format `price` per row using each row's currency so the
        // column emits a single ready-to-render string (the shared
        // format callback can't see the row).
        rows: vacantBerths.map((r) => ({
          ...r,
          price: r.price !== null ? formatMoney(r.price, r.currency) : '—',
        })),
      },
      {
        title: 'Stuck signing',
        columns: [
          { key: 'documentType', label: 'Document type' },
          { key: 'title', label: 'Title' },
          { key: 'clientName', label: 'Client' },
          { key: 'sentAt', label: 'Sent at', format: (v) => String(v).slice(0, 10) },
          { key: 'daysOutstanding', label: 'Days outstanding', align: 'right' },
        ],
        rows: stuckSigning.map((r) => ({ ...r })),
      },
      {
        title: 'Highest-value vacant berths',
        columns: [
          { key: 'mooring', label: 'Mooring' },
          { key: 'price', label: 'Price', align: 'right' },
        ],
        rows: highestValueVacant.map((r) => ({
          ...r,
          price: formatMoney(r.price, r.currency),
        })),
      },
    ],
  };
}
