import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import { TaxSlabEntity } from '../../entities/tax-slab.entity';
import { CLS_TENANT_ID } from '../../common/cls/cls.constants';

/**
 * TaxSnapshotService — single source of truth for the per-line tax
 * snapshot persisted on every quote / booking line item.
 *
 * Stakeholder policy locked 2026-05-07 (see docs/dmc_scope.md "F"):
 *
 *   Q1 = C → snapshot at quote save AND re-snapshot at booking-create
 *            from quote (booking save thereafter preserves)
 *   Q2 = B → snapshot is immutable on edit; explicit `refresh_tax: true`
 *            flag re-fetches the current slab. Frozen unit is the
 *            percentage components, not the absolute amount
 *   Q3 = A → tax computed in selling currency (caller passes the
 *            per-line subtotal in selling currency)
 *
 * Snapshot shape:
 *   tax_breakdown: {
 *     "<tax_type_id>": {
 *       "<component_id>": <rate_pct>   // e.g. CGST at 9 → 9
 *     }
 *   }
 *
 * Total rate (sum of all rates across all types/components) is
 * derived on demand by `sumRates(breakdown)`; `computeAmount` uses it
 * to extract the tax already embedded in a tax-inclusive subtotal
 * (all prices are inclusive — see DefaultTaxInclusiveTrue migration).
 */
@Injectable()
export class TaxSnapshotService {
  constructor(
    @InjectRepository(TaxSlabEntity) private readonly slabRepo: Repository<TaxSlabEntity>,
    private readonly cls: ClsService,
  ) {}

  private tenantId(): string {
    return this.cls.get<string>(CLS_TENANT_ID);
  }

  /**
   * Load the active slabs for a destination and freeze them as a
   * snapshot. Returns null when no destination is supplied or no
   * active slabs exist — caller should leave the line's tax_breakdown
   * + tax_amount NULL in that case (UI renders "Tax not configured").
   */
  async resolveForDestination(destinationId: string | null | undefined): Promise<{
    tax_breakdown: Record<string, Record<string, number>>;
    total_rate: number;
  } | null> {
    if (!destinationId) return null;
    const tenantId = this.tenantId();
    const slabs = await this.slabRepo.find({
      where: {
        tenant_id: tenantId,
        destination_id: destinationId,
        is_deleted: false,
        is_active: true,
      },
    });
    if (slabs.length === 0) return null;

    const breakdown: Record<string, Record<string, number>> = {};
    for (const slab of slabs) {
      const rates = slab.component_rates || {};
      // Only freeze components that actually have a non-zero rate set
      // — empty / null entries shouldn't contribute to the total or
      // clutter the customer-facing breakdown UI.
      const cleaned: Record<string, number> = {};
      for (const [compId, rate] of Object.entries(rates)) {
        if (rate != null && Number(rate) !== 0) cleaned[compId] = Number(rate);
      }
      if (Object.keys(cleaned).length > 0) {
        breakdown[slab.tax_type_id] = cleaned;
      }
    }
    if (Object.keys(breakdown).length === 0) return null;

    return {
      tax_breakdown: breakdown,
      total_rate: this.sumRates(breakdown),
    };
  }

  /**
   * Sum every component rate across every tax type. Used by
   * `computeAmount` to extract the embedded tax from an inclusive
   * subtotal, and for "(Refresh available — slab updated)" detection
   * on the FE (compare line's frozen total vs current slab's total).
   */
  sumRates(breakdown: Record<string, Record<string, number>> | null | undefined): number {
    if (!breakdown) return 0;
    let total = 0;
    for (const components of Object.values(breakdown)) {
      for (const rate of Object.values(components || {})) {
        const n = Number(rate);
        if (!Number.isNaN(n)) total += n;
      }
    }
    return total;
  }

  /**
   * Compute the absolute tax amount embedded in a line's subtotal
   * (in selling currency), given a frozen breakdown.
   *
   * Subtotals are tax-INCLUSIVE (all prices inclusive going forward —
   * see DefaultTaxInclusiveTrue migration), so the tax is EXTRACTED,
   * not added on top:
   *
   *   tax = subtotal − subtotal / (1 + total/100)
   *       = subtotal × total / (100 + total)
   *
   * e.g. subtotal 118 @ 18% → 18.00 (base 100). The old
   * `subtotal × total / 100` taxed an already-taxed amount and
   * overstated tax by a factor of (100 + total) / 100.
   *
   * Two-decimal rounding so the persisted column matches what the UI
   * renders via bifurcateInclusive.
   */
  computeAmount(
    subtotal: number | null | undefined,
    breakdown: Record<string, Record<string, number>> | null | undefined,
  ): number | null {
    if (subtotal == null || breakdown == null) return null;
    const total = this.sumRates(breakdown);
    if (total === 0) return 0;
    return Math.round(((Number(subtotal) * total) / (100 + total)) * 100) / 100;
  }

  /**
   * Decide whether a line needs a fresh snapshot or should preserve
   * its existing one. Q2=B: preserve unless the caller explicitly
   * flagged refresh_tax, OR the line has no snapshot yet (brand new
   * line on a save).
   */
  shouldSnapshot(line: { tax_breakdown?: unknown }, refreshFlag?: boolean): boolean {
    if (refreshFlag === true) return true;
    return !line?.tax_breakdown;
  }
}
