/**
 * Reporting domain model — the pure logic behind the Reporting page.
 *
 * See CONTEXT.md (Reporting: Gross cost, Net cost, Project delivery,
 * Current stock; Baseline performance: Likely downgrade, Likely upgrade)
 * and ADR-0010 (the compliance-window filter is a report-view parameter).
 * PRD: issue #370.
 */

import { EPC_BANDS, GOALS, type EpcBand } from "@/lib/epc/bands";

/**
 * Project delivery is not yet a data point: it is estimated as a fixed
 * share of construction works. Disclosed wherever gross cost is shown.
 */
export const PROJECT_DELIVERY_RATE = 0.3;

export interface LedgerInput {
  constructionCost: number;
  contingency: number;
  funding: number;
  homesUpgraded: number;
}

export interface Ledger {
  constructionCost: number;
  projectDelivery: number;
  contingency: number;
  funding: number;
  grossCost: number;
  netCost: number;
  grossPerHome: number;
}

export function computeLedger(input: LedgerInput): Ledger {
  const projectDelivery = input.constructionCost * PROJECT_DELIVERY_RATE;
  const grossCost = input.constructionCost + projectDelivery + input.contingency;

  return {
    constructionCost: input.constructionCost,
    projectDelivery,
    contingency: input.contingency,
    funding: input.funding,
    grossCost,
    netCost: grossCost - input.funding,
    grossPerHome: input.homesUpgraded > 0 ? grossCost / input.homesUpgraded : 0,
  };
}

/* ------------------------------------------------------------------
   Compliance window — ADR-0010
------------------------------------------------------------------ */

export interface CertificateEvidence {
  provenance: "lodged" | "predicted";
  lodgedBand: EpcBand | null;
  expiryDate: Date | null;
}

export interface ComplianceWindow {
  band: EpcBand;
  date: Date;
}

/** A is better than B; lower index = better band. */
function bandAtLeast(band: EpcBand, threshold: EpcBand): boolean {
  return EPC_BANDS.indexOf(band) <= EPC_BANDS.indexOf(threshold);
}

/**
 * A home may be skipped from a scenario's works view iff its lodged
 * certificate demonstrates the window band or better AND outlives the
 * window date (ADR-0010).
 */
export function isCompliantBeyondWindow(
  evidence: CertificateEvidence,
  window: ComplianceWindow,
): boolean {
  // A predicted home has no real certificate — its lodged_* values are
  // mirrored estimates (CONTEXT.md, EPC provenance) and never qualify.
  if (evidence.provenance !== "lodged") {
    return false;
  }
  if (evidence.lodgedBand === null || evidence.expiryDate === null) {
    return false;
  }
  return (
    bandAtLeast(evidence.lodgedBand, window.band) &&
    evidence.expiryDate.getTime() > window.date.getTime()
  );
}

/* ------------------------------------------------------------------
   Band movement — Likely downgrade / Likely upgrade (CONTEXT.md)
------------------------------------------------------------------ */

export type BandMovement = "likely-downgrade" | "likely-upgrade" | "none";

export interface BandMovementInput {
  provenance: "lodged" | "predicted";
  lodgedBand: EpcBand | null;
  effectiveBand: EpcBand | null;
}

/**
 * Compares the certificate's band with the effective (modelled) band.
 * Lodged above effective → a re-survey would likely certificate lower;
 * effective above lodged → a re-survey alone would likely certificate
 * higher. Band movement only — SAP drift within a band never qualifies.
 */
export function classifyBandMovement(input: BandMovementInput): BandMovement {
  // No real certificate (predicted) or an incomplete picture → no signal.
  if (
    input.provenance !== "lodged" ||
    input.lodgedBand === null ||
    input.effectiveBand === null
  ) {
    return "none";
  }

  const lodged = EPC_BANDS.indexOf(input.lodgedBand);
  const effective = EPC_BANDS.indexOf(input.effectiveBand);

  if (lodged < effective) return "likely-downgrade";
  if (lodged > effective) return "likely-upgrade";
  return "none";
}

/* ------------------------------------------------------------------
   Goal-aware callout
------------------------------------------------------------------ */

/** EPC-goal portfolios carry no band value; C is the sector default. */
export const DEFAULT_GOAL_BAND: EpcBand = "C";

export type GoalDimension = "carbon" | "energy" | "valuation";

export type GoalCallout =
  | { kind: "below-band"; band: EpcBand; count: number }
  | { kind: "dimension-total"; dimension: GoalDimension }
  | { kind: "below-band-movement"; band: EpcBand; before: number; after: number }
  | { kind: "dimension-movement"; dimension: GoalDimension };

const GOAL_DIMENSIONS: Partial<Record<string, GoalDimension>> = {
  [GOALS.CO2]: "carbon",
  [GOALS.ENERGY]: "energy",
  [GOALS.VALUATION]: "valuation",
};

/**
 * Index of the best value in a compare row (Screen D). Nulls — baseline
 * columns and missing data — never win. Returns null when nothing is
 * comparable, so no cell is marked.
 */
export function pickBestIndex(
  values: (number | null)[],
  direction: "lower" | "higher",
): number | null {
  let bestIndex: number | null = null;
  let best = direction === "lower" ? Infinity : -Infinity;
  values.forEach((v, i) => {
    if (v === null) return;
    if (direction === "lower" ? v < best : v > best) {
      best = v;
      bestIndex = i;
    }
  });
  return bestIndex;
}

/** Collapses baseline band rows (actual + estimated) into a plain band→count map. */
export function toBandCounts(
  bands: { epc: string; actual: number; estimated: number }[],
): Record<string, number> {
  const counts: Record<string, number> = {};
  for (const b of bands) {
    counts[b.epc] = (counts[b.epc] ?? 0) + b.actual + b.estimated;
  }
  return counts;
}

/** Counts homes in bands strictly worse than the target; Unknown never counts. */
export function countBelowBand(
  bandCounts: Record<string, number>,
  band: EpcBand,
): number {
  return EPC_BANDS.filter((b) => EPC_BANDS.indexOf(b) > EPC_BANDS.indexOf(band))
    .reduce((sum, b) => sum + (bandCounts[b] ?? 0), 0);
}

export type GoalCalloutInput =
  | {
      view: "current-stock";
      goal: string;
      bandCounts: Record<string, number>;
    }
  | {
      view: "scenario";
      goal: string;
      goalValue: string | null;
      bandCounts: Record<string, number>;
      scenarioBandCounts: Record<string, number>;
    };

export function selectGoalCallout(input: GoalCalloutInput): GoalCallout {
  const dimension = GOAL_DIMENSIONS[input.goal];

  if (input.view === "current-stock") {
    if (dimension) {
      return { kind: "dimension-total", dimension };
    }
    return {
      kind: "below-band",
      band: DEFAULT_GOAL_BAND,
      count: countBelowBand(input.bandCounts, DEFAULT_GOAL_BAND),
    };
  }

  if (dimension) {
    return { kind: "dimension-movement", dimension };
  }
  const band = (input.goalValue as EpcBand) ?? DEFAULT_GOAL_BAND;
  return {
    kind: "below-band-movement",
    band,
    before: countBelowBand(input.bandCounts, band),
    after: countBelowBand(input.scenarioBandCounts, band),
  };
}

/* ------------------------------------------------------------------
   Carbon equivalents & KPI deltas
------------------------------------------------------------------ */

/**
 * Average annual tailpipe CO₂ of a UK car (BEIS/DEFRA GHG conversion
 * factors, average car at average mileage ≈ 1.8 tCO₂e/yr). Methodology
 * pending sign-off (PRD #370) — cite the source wherever this renders.
 */
export const TONNES_CO2_PER_CAR_PER_YEAR = 1.8;

export function carsOffTheRoad(tonnesPerYear: number): number {
  if (tonnesPerYear <= 0) return 0;
  return Math.round(tonnesPerYear / TONNES_CO2_PER_CAR_PER_YEAR);
}

export interface KpiDelta {
  delta: number;
  improved: boolean;
}

export function shapeKpiDelta(input: {
  current: number;
  after: number;
  improvesWhenLower: boolean;
}): KpiDelta {
  const delta = input.after - input.current;
  const improved = input.improvesWhenLower ? delta < 0 : delta > 0;
  return { delta, improved };
}

/* ------------------------------------------------------------------
   Board headline
------------------------------------------------------------------ */

/** £2,477,000 → "£2.48m"; £850,000 → "£850k"; £900 → "£900". */
export function formatMoneyCompact(amount: number): string {
  const abs = Math.abs(amount);
  const sign = amount < 0 ? "-" : "";
  if (abs >= 1_000_000) return `${sign}£${(abs / 1_000_000).toFixed(2)}m`;
  if (abs >= 1_000) return `${sign}£${Math.round(abs / 1_000)}k`;
  return `${sign}£${Math.round(abs)}`;
}

export interface HeadlineInput {
  goal: string;
  goalValue: string | null;
  homesUpgraded: number;
  netCost: number;
  carbonSavedPerYear: number;
}

/**
 * One board-quotable sentence per scenario. Opens the scenario view and
 * the PDF; keep it plain enough to read aloud.
 */
export function buildHeadline(input: HeadlineInput): string {
  const money = formatMoneyCompact(input.netCost);
  const tonnes = Math.round(input.carbonSavedPerYear);
  const cars = carsOffTheRoad(input.carbonSavedPerYear);
  const carClause = ` — like taking ${cars} cars off the road.`;

  if (input.goal === GOALS.EPC) {
    const base = `Reaching EPC ${input.goalValue ?? DEFAULT_GOAL_BAND} across ${input.homesUpgraded} homes costs ${money} net`;
    return tonnes > 0
      ? `${base} and cuts carbon by ${tonnes} tonnes a year${carClause}`
      : `${base}.`;
  }

  if (input.goal === GOALS.CO2) {
    const base = `Cutting carbon by ${tonnes} tonnes a year across ${input.homesUpgraded} homes costs ${money} net`;
    return tonnes > 0 ? `${base}${carClause}` : `${base}.`;
  }

  const lead =
    input.goal === GOALS.ENERGY
      ? "Cutting energy use"
      : "Improving valuation";
  const base = `${lead} across ${input.homesUpgraded} homes costs ${money} net`;
  return tonnes > 0
    ? `${base} and cuts carbon by ${tonnes} tonnes a year${carClause}`
    : `${base}.`;
}
