"""
Accounting Quality / Red Flags (StockLab overhaul, Part 8 — new module).

Deliberately **not** wired into Overall Score, any of the five pillars, or the Buy/Sell
recommendation engine — the spec asks for this to start as a separate, informational layer, and
this module is not imported by `app/engines/scoring/*` or `app/engines/recommendation/*` (verified
by grep, re-verified in Part A8; see docs/AUDIT_ACCOUNTING_QUALITY_A8.md). It surfaces flags for a human (or a future,
separately-designed scoring pass) to weigh, not a score that silently moves the Investment Score.

Every flag below is computed from a `LineItems`/`FinancialSnapshot` field this codebase actually
ingests. Nothing here is derived from a field no adapter populates.

UPDATE (final engineering pass, Part A8 — docs/AUDIT_ACCOUNTING_QUALITY_A8.md): the two checks
previously listed as NOT IMPLEMENTED ("one_off_items", "acquisition_dependence") are now
implemented as checks #9 and #10, under names that state what they actually measure:

- `non_operating_earnings_reliance` — the residual between pre-tax income and operating income
  (adjusted for interest) IS, by construction, the below-the-line/non-recurring bucket. This is
  arithmetic on three fields FMP already maps (`incomeBeforeTax`, `operatingIncome`,
  `interestExpense`), not a proxy invented for a field that doesn't exist.
- `acquisition_driven_growth` — goodwill arises only from acquisitions, so a material year-over-
  year increase in goodwill IS the balance-sheet footprint of M&A. `goodwill` is mapped from FMP
  (`goodwill`) and populated by the demo adapter.

What is still deliberately NOT implemented, and why, is listed in
`AccountingQualityReport.checks_not_implemented` — an itemized special-charges breakdown
(restructuring/impairment/legal settlements shown separately) and the literal
cash-paid-for-acquisitions line. Both need statement fields no adapter in this repo maps today;
FMP's exact field name for cash paid for acquisitions could NOT be confirmed from its public
documentation in this pass (see the audit doc's research section), and guessing an API field name
is exactly what this project forbids.

Sign convention assumed by check #9: `interest_expense` is stored as a POSITIVE magnitude — the
same assumption `app/engines/metrics/core.py::interest_coverage` already makes
(`ebit / interest_expense`). If a future adapter stores it signed, check #9's residual flips
meaning; the raw residual is therefore recorded in the flag's `evidence` so it is auditable.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

from app.engines.metrics.formula_utils import safe_div, trend_slope, yoy
from app.engines.types import FinancialSnapshot


@dataclass
class RedFlag:
    key: str
    severity: str  # "LOW" | "MEDIUM" | "HIGH"
    description: str
    evidence: dict


@dataclass
class AccountingQualityReport:
    flags: list[RedFlag] = field(default_factory=list)
    checks_run: list[str] = field(default_factory=list)
    checks_skipped_insufficient_data: list[str] = field(default_factory=list)
    checks_not_implemented: list[str] = field(default_factory=lambda: [
        "itemized_special_charges — LineItems has no restructuring/impairment/legal-settlement "
        "breakdown; check #9 (non_operating_earnings_reliance) measures the aggregate "
        "below-the-line residual instead, which is what the ingested fields actually support",
        "cash_paid_for_acquisitions — no adapter in this repo maps a cash-flow acquisitions line, "
        "and FMP's field name for it could not be confirmed from public documentation in this "
        "pass; check #10 (acquisition_driven_growth) uses the goodwill delta instead, which is a "
        "balance-sheet consequence of the same activity",
        "stock_based_compensation_burden — the DB column and ingest field exist and the demo "
        "adapter populates them, but app/adapters/fmp.py does NOT map any FMP field to "
        "stock_based_compensation, so this check would pass in demo and be permanently skipped "
        "against real data. Not written rather than written-and-always-skipped.",
    ])

    @property
    def flag_count_by_severity(self) -> dict[str, int]:
        out = {"LOW": 0, "MEDIUM": 0, "HIGH": 0}
        for f in self.flags:
            out[f.severity] = out.get(f.severity, 0) + 1
        return out


def _series(snapshot: FinancialSnapshot, field_name: str) -> list[Optional[float]]:
    return [getattr(snapshot.current, field_name)] + [getattr(h, field_name) for h in snapshot.history]


def assess_accounting_quality(snapshot: FinancialSnapshot) -> AccountingQualityReport:
    report = AccountingQualityReport()
    c = snapshot.current

    # 1. Cash-flow quality: OCF vs Net Income (accrual-heavy earnings if OCF persistently trails NI)
    report.checks_run.append("cash_flow_quality")
    if c.operating_cash_flow is not None and c.net_income is not None and c.net_income > 0:
        ratio = safe_div(c.operating_cash_flow, c.net_income)
        if ratio is not None and ratio < 0.7:
            report.flags.append(RedFlag(
                "cash_flow_quality", "HIGH" if ratio < 0.4 else "MEDIUM",
                f"Operating cash flow ({c.operating_cash_flow:.0f}) covers only "
                f"{ratio * 100:.0f}% of net income ({c.net_income:.0f}) — earnings may be less "
                f"cash-backed than reported.",
                {"ocf": c.operating_cash_flow, "net_income": c.net_income, "ratio": ratio},
            ))
    else:
        report.checks_skipped_insufficient_data.append("cash_flow_quality")

    # 2. FCF vs NI divergence (a distinct check from #1 — nets out capex, not just OCF)
    report.checks_run.append("fcf_vs_net_income_divergence")
    if c.operating_cash_flow is not None and c.capital_expenditure is not None and c.net_income:
        fcf = c.operating_cash_flow - c.capital_expenditure
        ratio = safe_div(fcf, c.net_income)
        if c.net_income > 0 and ratio is not None and ratio < 0.5:
            report.flags.append(RedFlag(
                "fcf_vs_net_income_divergence", "MEDIUM",
                f"Free cash flow ({fcf:.0f}) is well below net income ({c.net_income:.0f}) — "
                f"capex or working-capital consumption is eating reported earnings.",
                {"fcf": fcf, "net_income": c.net_income, "ratio": ratio},
            ))
    else:
        report.checks_skipped_insufficient_data.append("fcf_vs_net_income_divergence")

    # 3/4. Receivables and inventory growth vs revenue growth
    for field_name, label in (("receivables", "receivables_vs_revenue_growth"),
                               ("inventory", "inventory_vs_revenue_growth")):
        report.checks_run.append(label)
        rev_series = _series(snapshot, "revenue")
        field_series = _series(snapshot, field_name)
        rev_growth = yoy(rev_series[0], rev_series[1]) if len(rev_series) > 1 else None
        field_growth = yoy(field_series[0], field_series[1]) if len(field_series) > 1 else None
        if rev_growth is None or field_growth is None:
            report.checks_skipped_insufficient_data.append(label)
            continue
        gap = field_growth - rev_growth
        if gap > 0.15:  # growing 15+ points faster than revenue
            report.flags.append(RedFlag(
                label, "HIGH" if gap > 0.30 else "MEDIUM",
                f"{field_name.capitalize()} grew {field_growth * 100:.1f}% YoY vs. revenue "
                f"{rev_growth * 100:.1f}% — a {gap * 100:.1f}pt gap can indicate channel "
                f"stuffing, slowing collections, or demand weakness masked by revenue timing.",
                {"revenue_growth": rev_growth, f"{field_name}_growth": field_growth, "gap": gap},
            ))

    # 5. Debt growth outpacing the business
    report.checks_run.append("debt_growth")
    debt_series = _series(snapshot, "total_debt")
    debt_growth = yoy(debt_series[0], debt_series[1]) if len(debt_series) > 1 else None
    if debt_growth is None:
        report.checks_skipped_insufficient_data.append("debt_growth")
    elif debt_growth > 0.30:
        report.flags.append(RedFlag(
            "debt_growth", "MEDIUM" if debt_growth < 0.60 else "HIGH",
            f"Total debt grew {debt_growth * 100:.1f}% YoY.",
            {"debt_growth": debt_growth},
        ))

    # 6. Dilution
    report.checks_run.append("share_dilution")
    shares_series = _series(snapshot, "diluted_shares")
    share_growth = yoy(shares_series[0], shares_series[1]) if len(shares_series) > 1 else None
    if share_growth is None:
        report.checks_skipped_insufficient_data.append("share_dilution")
    elif share_growth > 0.05:
        report.flags.append(RedFlag(
            "share_dilution", "MEDIUM" if share_growth < 0.10 else "HIGH",
            f"Diluted share count grew {share_growth * 100:.1f}% YoY.",
            {"share_count_growth": share_growth},
        ))

    # 7. Goodwill concentration
    report.checks_run.append("goodwill_concentration")
    if c.goodwill is not None and c.total_assets:
        ratio = safe_div(c.goodwill, c.total_assets)
        if ratio is not None and ratio > 0.30:
            report.flags.append(RedFlag(
                "goodwill_concentration", "MEDIUM" if ratio < 0.50 else "HIGH",
                f"Goodwill is {ratio * 100:.0f}% of total assets — significant impairment risk "
                f"concentrated in acquisition accounting rather than operating assets.",
                {"goodwill": c.goodwill, "total_assets": c.total_assets, "ratio": ratio},
            ))
    else:
        report.checks_skipped_insufficient_data.append("goodwill_concentration")

    # 8. Margin deterioration trend
    report.checks_run.append("margin_deterioration_trend")
    om_hist = [safe_div(h.operating_income, h.revenue) for h in ([c] + list(snapshot.history))]
    om_hist_ordered = list(reversed(om_hist))
    slope = trend_slope(om_hist_ordered)
    if slope is None:
        report.checks_skipped_insufficient_data.append("margin_deterioration_trend")
    elif slope < -0.05:
        report.flags.append(RedFlag(
            "margin_deterioration_trend", "MEDIUM" if slope > -0.10 else "HIGH",
            f"Operating margin trend is deteriorating (normalized slope {slope:.3f}).",
            {"trend_slope": slope, "periods": len(om_hist_ordered)},
        ))

    # 9. Non-operating / below-the-line earnings reliance (Part A8).
    #    pretax_income = operating_income - interest_expense + <everything else>. That
    #    "everything else" residual is where one-off gains/losses, asset sales, litigation
    #    settlements, FX and investment income land. A large residual relative to pre-tax income
    #    means reported profit is NOT primarily coming from operations this period.
    report.checks_run.append("non_operating_earnings_reliance")
    if c.pretax_income is not None and c.operating_income is not None and c.pretax_income != 0:
        interest = c.interest_expense or 0.0
        residual = c.pretax_income - c.operating_income + interest
        share = safe_div(abs(residual), abs(c.pretax_income))
        # Materiality floor. `share` alone is a ratio against a denominator that can be
        # arbitrarily close to zero: a company with a thin pre-tax profit trips a 25% threshold on
        # a residual that is a rounding error at revenue scale. Requiring the residual to ALSO be
        # at least 1% of revenue keeps the check from firing on noise. The 1% figure is a
        # judgment call, stated as such -- it was NOT tuned against the test fixtures (applying it
        # changes the flag outcome for none of the 8 real-shaped fixtures in
        # tests/fixtures/company_profiles.py; verified by running them).
        material = c.revenue is None or abs(residual) >= 0.01 * abs(c.revenue)
        if share is not None and share > 0.25 and material:
            report.flags.append(RedFlag(
                "non_operating_earnings_reliance", "HIGH" if share > 0.50 else "MEDIUM",
                f"{share * 100:.0f}% of pre-tax income ({c.pretax_income:.0f}) comes from outside "
                f"operating income ({c.operating_income:.0f}) — a "
                f"{'gain' if residual > 0 else 'charge'} of {residual:.0f} sits below the "
                f"operating line. Non-recurring items, asset sales, investment income or "
                f"litigation can all land here; reported earnings are less repeatable than the "
                f"headline suggests.",
                {
                    "pretax_income": c.pretax_income,
                    "operating_income": c.operating_income,
                    "interest_expense": c.interest_expense,
                    "non_operating_residual": residual,
                    "share_of_pretax": share,
                },
            ))
    else:
        report.checks_skipped_insufficient_data.append("non_operating_earnings_reliance")

    # 10. Acquisition-driven growth (Part A8).
    #     Goodwill only ever arises from acquiring a business above its identifiable net asset
    #     value, so a material jump in goodwill is the balance-sheet record of M&A in that period.
    #     Paired with revenue growth, it separates "grew by building" from "grew by buying".
    report.checks_run.append("acquisition_driven_growth")
    gw_series = _series(snapshot, "goodwill")
    rev_series_a = _series(snapshot, "revenue")
    prior_assets = snapshot.history[0].total_assets if snapshot.history else None
    if (
        len(gw_series) > 1
        and gw_series[0] is not None
        and gw_series[1] is not None
        and prior_assets
    ):
        gw_delta = gw_series[0] - gw_series[1]
        intensity = safe_div(gw_delta, prior_assets)
        rev_growth_a = yoy(rev_series_a[0], rev_series_a[1]) if len(rev_series_a) > 1 else None
        if intensity is not None and intensity > 0.05:
            severity = "HIGH" if (intensity > 0.15 or (rev_growth_a or 0) > 0.25) else "MEDIUM"
            growth_note = (
                f" over the same period revenue grew {rev_growth_a * 100:.1f}%, so that growth is "
                f"unlikely to be wholly organic."
                if rev_growth_a is not None and rev_growth_a > 0.05
                else " revenue did not grow materially despite the spend."
                if rev_growth_a is not None
                else ""
            )
            report.flags.append(RedFlag(
                "acquisition_driven_growth", severity,
                f"Goodwill rose {gw_delta:.0f} year over year, equal to {intensity * 100:.1f}% of "
                f"prior-year total assets — a material acquisition footprint.{growth_note}",
                {
                    "goodwill_current": gw_series[0],
                    "goodwill_prior": gw_series[1],
                    "goodwill_delta": gw_delta,
                    "prior_total_assets": prior_assets,
                    "goodwill_delta_over_prior_assets": intensity,
                    "revenue_growth": rev_growth_a,
                },
            ))
    else:
        report.checks_skipped_insufficient_data.append("acquisition_driven_growth")

    return report
