"""Tests for the companyfacts -> snapshot parser, on a realistic Apple-shaped
fixture (the structure verified by live probe)."""

from __future__ import annotations

import os
from datetime import date, datetime, timezone

import pytest

from app.services.sec_facts_parser import (
    Fact,
    FilingMeta,
    _compose_cash,
    _compose_debt,
    _fiscal_context,
    _select_shares,
    parse_snapshots,
)

UTC = timezone.utc


def _dur(start, end, val, accn, fy=2026, fp="Q2"):
    return {"start": start, "end": end, "val": val, "fy": fy, "fp": fp, "accn": accn, "form": "10-Q"}


def _inst(end, val, accn, fy=2026, fp="Q2"):
    return {"end": end, "val": val, "fy": fy, "fp": fp, "accn": accn, "form": "10-Q"}


COMPANYFACTS = {
    "cik": 320193,
    "facts": {
        "us-gaap": {
            "RevenueFromContractWithCustomerExcludingAssessedTax": {"units": {"USD": [
                _dur("2025-09-28", "2025-12-27", 143756, "A", fp="Q1"),   # Q1 discrete == YTD
                _dur("2025-09-28", "2026-03-28", 254940, "B"),            # Q2 YTD (181d)  <- want this
                _dur("2025-12-28", "2026-03-28", 111184, "B"),            # Q2 discrete (90d)
            ]}},
            "NetIncomeLoss": {"units": {"USD": [_dur("2025-09-28", "2026-03-28", 40000, "B")]}},
            # only a discrete-length fact for Q2 -> must be null, not the discrete
            "OperatingIncomeLoss": {"units": {"USD": [_dur("2025-12-28", "2026-03-28", 30000, "B")]}},
            "EarningsPerShareDiluted": {"units": {"USD/shares": [_dur("2025-09-28", "2026-03-28", 2.55, "B")]}},
            "CashAndCashEquivalentsAtCarryingValue": {"units": {"USD": [_inst("2026-03-28", 30000, "B")]}},
            "MarketableSecuritiesCurrent": {"units": {"USD": [_inst("2026-03-28", 20000, "B")]}},
            "LongTermDebtNoncurrent": {"units": {"USD": [_inst("2026-03-28", 80000, "B")]}},
            "LongTermDebtCurrent": {"units": {"USD": [_inst("2026-03-28", 10000, "B")]}},
        },
        "dei": {
            "EntityCommonStockSharesOutstanding": {"units": {"shares": [
                {"end": "2026-04-17", "val": 14687356000, "fy": 2026, "fp": "Q2", "accn": "B", "form": "10-Q"},
            ]}},
        },
    },
}

FILINGS = {
    "A": FilingMeta(date(2025, 12, 27), date(2026, 1, 30), datetime(2026, 1, 30, 11, 1, tzinfo=UTC), "10-Q"),
    "B": FilingMeta(date(2026, 3, 28), date(2026, 5, 1), datetime(2026, 5, 1, 10, 1, tzinfo=UTC), "10-Q"),
}


def _by_accn(rows):
    return {r.accession: r for r in rows}


def test_parses_ytd_not_discrete_and_cover_date_shares():
    res = parse_snapshots(COMPANYFACTS, FILINGS, {"A", "B"})
    assert not res.skipped_filings and not res.field_issues
    b = _by_accn(res.rows)["B"]

    assert (b.cik, b.fiscal_year, b.fiscal_period) == ("0000320193", 2026, "Q2")
    assert b.period_end == date(2026, 3, 28)
    assert b.period_start == date(2025, 9, 28)  # YTD start (fiscal-year start)
    assert b.revenue == 254940          # the 6-month YTD, NOT the 111184 discrete
    assert b.net_income == 40000
    assert b.operating_income is None   # only a discrete-length fact existed -> null
    assert b.diluted_eps == 2.55
    # cash + first-present ST investment (MarketableSecuritiesCurrent), each once
    assert b.cash_and_st_investments == 50000
    # long-term parts summed (no aggregate, no short-term)
    assert b.total_debt == 90000
    assert b.shares_outstanding == 14687356000
    assert b.shares_outstanding_date == date(2026, 4, 17)  # cover date != period_end
    assert b.filed_date == date(2026, 5, 1)
    assert b.accepted_at == datetime(2026, 5, 1, 10, 1, tzinfo=UTC)


def test_q1_discrete_is_the_ytd():
    res = parse_snapshots(COMPANYFACTS, FILINGS, {"A"})
    a = _by_accn(res.rows)["A"]
    assert a.fiscal_period == "Q1"
    assert a.revenue == 143756  # Q1 YTD == Q1 discrete
    assert a.period_start == date(2025, 9, 28)


def test_skips_filing_without_usable_period():
    cf = {
        "cik": 320193,
        "facts": {"us-gaap": {"NetIncomeLoss": {"units": {"USD": [
            {"start": "2025-09-28", "end": "2026-03-28", "val": 1, "fy": 2026, "fp": "H1", "accn": "X", "form": "10-Q"},
        ]}}}},
    }
    filings = {"X": FilingMeta(date(2026, 3, 28), date(2026, 5, 1), datetime(2026, 5, 1, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(cf, filings, {"X"})
    assert res.rows == []
    assert res.skipped_filings == [{"accession": "X", "reason": "no usable period identity"}]


def test_missing_accession_is_skipped():
    res = parse_snapshots(COMPANYFACTS, FILINGS, {"NOPE"})
    assert res.rows == []
    assert res.skipped_filings == [{"accession": "NOPE", "reason": "no facts or filing metadata"}]


def test_debt_prefers_aggregate_over_parts():
    rd = date(2026, 3, 28)
    facts = [
        Fact("us-gaap", "LongTermDebt", "USD", None, rd, 95000, 2026, "Q2"),
        Fact("us-gaap", "LongTermDebtNoncurrent", "USD", None, rd, 80000, 2026, "Q2"),
        Fact("us-gaap", "LongTermDebtCurrent", "USD", None, rd, 10000, 2026, "Q2"),
        Fact("us-gaap", "CommercialPaper", "USD", None, rd, 5000, 2026, "Q2"),
    ]
    # aggregate (95000) used, parts ignored; + one short-term pick (5000)
    assert _compose_debt(facts, rd) == 100000


def test_cash_picks_one_st_investment_source():
    rd = date(2026, 3, 28)
    facts = [
        Fact("us-gaap", "CashAndCashEquivalentsAtCarryingValue", "USD", None, rd, 30000, 2026, "Q2"),
        Fact("us-gaap", "ShortTermInvestments", "USD", None, rd, 15000, 2026, "Q2"),
        Fact("us-gaap", "MarketableSecuritiesCurrent", "USD", None, rd, 20000, 2026, "Q2"),
    ]
    # ShortTermInvestments is first in priority -> 30000 + 15000 (not both ST tags)
    assert _compose_cash(facts, rd) == 45000


RD = date(2026, 3, 28)


def _dei(end, val):
    return Fact("dei", "EntityCommonStockSharesOutstanding", "shares", None, end, val, 2026, "Q2")


def _gaap_shares(end, val):
    return Fact("us-gaap", "CommonStockSharesOutstanding", "shares", None, end, val, 2026, "Q2")


def test_shares_prefers_dei_cover_page():
    facts = [_dei(date(2026, 4, 17), 100), _gaap_shares(RD, 999)]
    assert _select_shares(facts, RD) == (100.0, date(2026, 4, 17), False)


def test_shares_falls_back_to_usgaap_at_report_date():
    # Alphabet case: no dei fact; us-gaap current + a prior comparative.
    facts = [_gaap_shares(date(2025, 12, 31), 888), _gaap_shares(RD, 12116)]
    assert _select_shares(facts, RD) == (12116.0, RD, False)  # comparative excluded


def test_shares_conflict_returns_null_ambiguous():
    facts = [_dei(RD, 100), _dei(RD, 200)]  # two differing consolidated values
    assert _select_shares(facts, RD) == (None, None, True)


def test_shares_never_uses_weighted_average():
    facts = [Fact("us-gaap", "WeightedAverageNumberOfDilutedSharesOutstanding", "shares", None, RD, 5, 2026, "Q2")]
    assert _select_shares(facts, RD) == (None, None, False)  # not a shares source


def test_conflicting_fiscal_context_is_rejected():
    facts = [
        Fact("us-gaap", "Revenues", "USD", date(2025, 9, 28), RD, 1, 2026, "Q2"),
        Fact("us-gaap", "NetIncomeLoss", "USD", date(2025, 9, 28), RD, 2, 2025, "Q3"),
    ]  # 1-1 tie between two contexts at reportDate
    assert _fiscal_context(facts, RD) == (None, None)
    # a clear majority wins
    facts.append(Fact("us-gaap", "OperatingIncomeLoss", "USD", date(2025, 9, 28), RD, 3, 2026, "Q2"))
    assert _fiscal_context(facts, RD) == (2026, "Q2")


def test_conflicting_context_skips_row():
    cf = {"cik": 1, "facts": {"us-gaap": {
        "Revenues": {"units": {"USD": [_dur("2025-09-28", "2026-03-28", 1, "B", fp="Q2")]}},
        "NetIncomeLoss": {"units": {"USD": [_dur("2025-09-28", "2026-03-28", 2, "B", fy=2025, fp="Q3")]}},
    }}}
    filings = {"B": FilingMeta(RD, date(2026, 5, 1), datetime(2026, 5, 1, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(cf, filings, {"B"})
    assert res.rows == []
    assert res.skipped_filings == [{"accession": "B", "reason": "no usable period identity"}]


def test_foreign_taxonomy_and_malformed_facts_ignored():
    cf = {"cik": 1, "facts": {
        "us-gaap": {
            "Revenues": {"units": {"USD": [_dur("2025-09-28", "2026-03-28", 500, "B")]}},
            "NetIncomeLoss": {"units": {"USD": [
                {"start": "2025-09-28", "end": "2026-03-28", "val": None, "fy": 2026, "fp": "Q2", "accn": "B"},
            ]}},
            "OperatingIncomeLoss": {"units": {"USD": [
                {"start": "2025-09-28", "end": "2026-03-28", "val": float("nan"), "fy": 2026, "fp": "Q2", "accn": "B"},
            ]}},
        },
        "acme": {"RevenueFromContractWithCustomerExcludingAssessedTax": {"units": {"USD": [
            _dur("2025-09-28", "2026-03-28", 99999, "B"),  # custom taxonomy — must be ignored
        ]}}},
    }}
    filings = {"B": FilingMeta(RD, date(2026, 5, 1), datetime(2026, 5, 1, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(cf, filings, {"B"})
    assert res.rows[0].revenue == 500          # us-gaap Revenues, not the acme concept
    assert res.rows[0].net_income is None      # val None ignored
    assert res.rows[0].operating_income is None  # NaN ignored


def test_ambiguous_shares_produces_row_plus_note():
    cf = {"cik": 1, "facts": {
        "us-gaap": {"Revenues": {"units": {"USD": [_dur("2025-09-28", "2026-03-28", 500, "B")]}}},
        "dei": {"EntityCommonStockSharesOutstanding": {"units": {"shares": [
            {"end": "2026-04-17", "val": 100, "fy": 2026, "fp": "Q2", "accn": "B"},
            {"end": "2026-04-17", "val": 200, "fy": 2026, "fp": "Q2", "accn": "B"},
        ]}}},
    }}
    filings = {"B": FilingMeta(RD, date(2026, 5, 1), datetime(2026, 5, 1, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(cf, filings, {"B"})
    assert len(res.rows) == 1 and res.rows[0].shares_outstanding is None  # row kept, shares null
    assert res.field_issues == [{"accession": "B", "reason": "ambiguous shares outstanding"}]
    assert res.skipped_filings == []  # a field issue is NOT a skipped filing


# Opt-in live check against real Apple companyfacts. Skips unless SEC_LIVE=1 and a
# real SEC_USER_AGENT are set (network + fair-access contact email).
@pytest.mark.skipif(
    not (os.environ.get("SEC_LIVE") and os.environ.get("SEC_USER_AGENT")),
    reason="set SEC_LIVE=1 + SEC_USER_AGENT to run the live SEC parser check",
)
async def test_live_apple_parse_invariants():
    from app.services.sec_client import SecClient

    def _dt(s):
        return datetime.fromisoformat(s.replace("Z", "+00:00")) if s else None

    async with SecClient(user_agent=os.environ["SEC_USER_AGENT"]) as c:
        cf = await c.companyfacts(320193)
        sub = await c.submissions(320193, include_history=False)

    filings = {
        f["accession"]: FilingMeta(
            date.fromisoformat(f["report_date"]),
            date.fromisoformat(f["filing_date"]),
            _dt(f["acceptance_datetime"]),
            f["form"],
        )
        for f in sub["filings"]
        if f["report_date"] and f["filing_date"] and f["acceptance_datetime"]
    }
    rows = parse_snapshots(cf, filings, set(filings)).rows
    assert len(rows) > 20
    assert all(r.period_end and r.fiscal_year and r.fiscal_period for r in rows)
    # YTD revenue is non-decreasing within a fiscal year
    by_fy: dict[int, list] = {}
    for r in rows:
        if r.revenue is not None:
            by_fy.setdefault(r.fiscal_year, []).append((r.fiscal_period, r.revenue))
    order = {"Q1": 1, "Q2": 2, "Q3": 3, "FY": 4}
    for fy, series in by_fy.items():
        series.sort(key=lambda x: order[x[0]])
        vals = [v for _, v in series]
        assert vals == sorted(vals), f"YTD revenue not monotonic in FY{fy}: {series}"
    # shares cover-date differs from period_end
    latest = max(rows, key=lambda r: r.period_end)
    assert latest.shares_outstanding_date != latest.period_end


# -- revenue concept coverage (A5 parity findings) ---------------------------

def _one_filing(concepts: dict, *, start: str, end: str, fp: str):
    """A single 10-Q whose facts are the given {concept: value} at one YTD span."""
    facts = {
        name: {"units": {"USD": [_dur(start, end, val, "X", fp=fp)]}}
        for name, val in concepts.items()
    }
    companyfacts = {"cik": 19617, "facts": {"us-gaap": facts}}
    filings = {
        "X": FilingMeta(
            date.fromisoformat(end), date(2026, 5, 1), datetime(2026, 5, 1, 10, tzinfo=UTC), "10-Q"
        )
    }
    return parse_snapshots(companyfacts, filings, {"X"})


def test_revenue_reads_banks_total_revenue_tag():
    # JPM/GS/WFC tag RevenuesNetOfInterestExpense in every 10-Q and never (or
    # only annually) `Revenues` -- previously null, so revenue growth was too.
    res = _one_filing(
        {"RevenuesNetOfInterestExpense": 49836}, start="2026-01-01", end="2026-03-31", fp="Q1"
    )
    assert res.rows[0].revenue == 49836


def test_revenue_reads_including_assessed_tax_variant():
    # ARE/KHC tag only the Including variant.
    res = _one_filing(
        {"RevenueFromContractWithCustomerIncludingAssessedTax": 671},
        start="2026-01-01", end="2026-03-31", fp="Q1",
    )
    assert res.rows[0].revenue == 671


def test_revenue_concept_priority_is_unchanged_by_the_added_tags():
    # The new entries are appended, so any issuer that already resolved keeps
    # the same concept -- only issuers that resolved to nothing gain a value.
    res = _one_filing(
        {
            "RevenueFromContractWithCustomerExcludingAssessedTax": 100,
            "RevenueFromContractWithCustomerIncludingAssessedTax": 110,
            "RevenuesNetOfInterestExpense": 120,
            "Revenues": 130,
        },
        start="2026-01-01", end="2026-03-31", fp="Q1",
    )
    assert res.rows[0].revenue == 100


def test_four_four_five_q3_ytd_span_is_accepted():
    # A 12/12/12/16-week filer's YTD-Q3 is 36 weeks = 251 days (COST 2026 Q3),
    # which missed the old 20-day tolerance around 273 by ~2 and dropped Q3
    # every year -- breaking the quarter chain and nulling TTM and YoY.
    res = _one_filing(
        {"RevenueFromContractWithCustomerExcludingAssessedTax": 207431},
        start="2025-09-01", end="2026-05-10", fp="Q3",
    )
    assert (date(2026, 5, 10) - date(2025, 9, 1)).days == 251
    assert res.rows[0].revenue == 207431


def test_eps_falls_back_to_continuing_operations_variant():
    # REG tags only this variant on every filing; FCX tags it in its 10-K while
    # using EarningsPerShareDiluted in its 10-Qs.
    companyfacts = {
        "cik": 910606,
        "facts": {"us-gaap": {"IncomeLossFromContinuingOperationsPerDilutedShare": {
            "units": {"USD/shares": [_dur("2026-01-01", "2026-03-31", 1.81, "X", fp="Q1")]}
        }}},
    }
    filings = {"X": FilingMeta(date(2026, 3, 31), date(2026, 5, 1),
                               datetime(2026, 5, 1, 10, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(companyfacts, filings, {"X"})
    assert res.rows[0].diluted_eps == 1.81


def test_eps_concept_priority_is_unchanged_by_the_added_tag():
    companyfacts = {
        "cik": 831259,
        "facts": {"us-gaap": {
            "EarningsPerShareDiluted": {
                "units": {"USD/shares": [_dur("2026-01-01", "2026-03-31", 0.61, "X", fp="Q1")]}},
            "IncomeLossFromContinuingOperationsPerDilutedShare": {
                "units": {"USD/shares": [_dur("2026-01-01", "2026-03-31", 0.75, "X", fp="Q1")]}},
        }},
    }
    filings = {"X": FilingMeta(date(2026, 3, 31), date(2026, 5, 1),
                               datetime(2026, 5, 1, 10, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(companyfacts, filings, {"X"})
    assert res.rows[0].diluted_eps == 0.61


# -- period identity from the fiscal calendar, not SEC's fy/fp ---------------

from app.services.sec_facts_parser import _period_identity  # noqa: E402


def _meta(end: str, form: str = "10-Q") -> FilingMeta:
    d = date.fromisoformat(end)
    return FilingMeta(d, d, datetime(d.year, d.month, d.day, tzinfo=UTC), form)


def test_a_10q_is_never_labelled_fy():
    # BXP: a 10-Q for period end 2026-03-31 carried fy/fp saying "2026 FY", which
    # collided with the real annual row and measured a 90-day fact against the
    # 365-day FY expectation.
    fy, fp = _period_identity(_meta("2026-03-31"), "1231")
    assert (fy, fp) == (2026, "Q1")


def test_december_filer_years_do_not_collide():
    # FRT: two 10-Ks, ending 2024-12-31 and 2025-12-31, both labelled "2024 FY".
    assert _period_identity(_meta("2024-12-31", "10-K"), "1231") == (2024, "FY")
    assert _period_identity(_meta("2025-12-31", "10-K"), "1231") == (2025, "FY")


def test_january_year_end_groups_its_quarters(): 
    # CRM/CRWD/WDAY: the year ending 2026-01-31 and its own quarters must share a
    # fiscal year, and must not collide with the year ending 2025-01-31.
    assert _period_identity(_meta("2026-01-31", "10-K"), "0131") == (2026, "FY")
    assert _period_identity(_meta("2025-01-31", "10-K"), "0131") == (2025, "FY")
    assert _period_identity(_meta("2025-04-30"), "0131") == (2026, "Q1")
    assert _period_identity(_meta("2025-07-31"), "0131") == (2026, "Q2")
    assert _period_identity(_meta("2025-10-31"), "0131") == (2026, "Q3")


def test_mid_year_end_orders_correctly():
    # STX: the year ending 2025-06-27 was labelled "2027 FY" and sorted after
    # quarters that precede it.
    assert _period_identity(_meta("2025-06-27", "10-K"), "0627") == (2025, "FY")
    assert _period_identity(_meta("2025-10-03"), "0627") == (2026, "Q1")
    assert _period_identity(_meta("2026-01-02"), "0627") == (2026, "Q2")
    assert _period_identity(_meta("2026-04-03"), "0627") == (2026, "Q3")


def test_four_four_five_quarters_place_correctly():
    # COST: a 12/12/12/16-week year leaves Q3 112 days from the year end, not 91.
    assert _period_identity(_meta("2025-11-23"), "0830") == (2026, "Q1")
    assert _period_identity(_meta("2026-02-15"), "0830") == (2026, "Q2")
    assert _period_identity(_meta("2026-05-10"), "0830") == (2026, "Q3")
    assert _period_identity(_meta("2026-08-30", "10-K"), "0830") == (2026, "FY")


def test_year_end_crossing_january_still_groups_one_year():
    # DPZ (fiscalYearEnd 0102): the label shifts by one against Domino's own
    # naming, which is fine -- a year and its quarters must simply agree.
    year, _ = _period_identity(_meta("2025-12-28", "10-K"), "0102")
    assert (year, "FY") == _period_identity(_meta("2025-12-28", "10-K"), "0102")
    assert _period_identity(_meta("2025-03-23"), "0102") == (year, "Q1")
    assert _period_identity(_meta("2025-06-15"), "0102") == (year, "Q2")
    assert _period_identity(_meta("2025-09-07"), "0102") == (year, "Q3")


def test_missing_fiscal_calendar_falls_back_to_filing_context():
    assert _period_identity(_meta("2026-03-31"), None) == (None, None)
    # ...and parse_snapshots then uses the fy/fp path, preserving old behaviour.
    res = parse_snapshots(COMPANYFACTS, FILINGS, {"B"})
    assert (res.rows[0].fiscal_year, res.rows[0].fiscal_period) == (2026, "Q2")



def test_eps_falls_back_to_basic_only_when_no_diluted_variant_exists():
    # PPL's 2026 Q1 tags no diluted EPS at all, only basic -- one missing period
    # broke the quarter chain and nulled TTM.
    companyfacts = {
        "cik": 922224,
        "facts": {"us-gaap": {"EarningsPerShareBasic": {
            "units": {"USD/shares": [_dur("2026-01-01", "2026-03-31", 0.60, "X", fp="Q1")]}
        }}},
    }
    filings = {"X": FilingMeta(date(2026, 3, 31), date(2026, 5, 1),
                               datetime(2026, 5, 1, 10, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(companyfacts, filings, {"X"}, fiscal_year_end="1231")
    assert res.rows[0].diluted_eps == 0.60


def test_diluted_still_wins_over_basic_when_both_present():
    companyfacts = {
        "cik": 320193,
        "facts": {"us-gaap": {
            "EarningsPerShareDiluted": {
                "units": {"USD/shares": [_dur("2026-01-01", "2026-03-31", 1.36, "X", fp="Q1")]}},
            "EarningsPerShareBasic": {
                "units": {"USD/shares": [_dur("2026-01-01", "2026-03-31", 1.40, "X", fp="Q1")]}},
        }},
    }
    filings = {"X": FilingMeta(date(2026, 3, 31), date(2026, 5, 1),
                               datetime(2026, 5, 1, 10, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(companyfacts, filings, {"X"}, fiscal_year_end="1231")
    assert res.rows[0].diluted_eps == 1.36


def test_weighted_average_shares_prefers_the_shortest_span():
    # A 10-Q carries both the quarter's average and the YTD one. The shorter
    # window sits closer to the current count, which is what market cap wants.
    companyfacts = {
        "cik": 1326801,
        "facts": {"us-gaap": {"WeightedAverageNumberOfDilutedSharesOutstanding": {
            "units": {"shares": [
                _dur("2026-01-01", "2026-09-30", 2_600_000_000, "X", fp="Q3"),  # YTD
                _dur("2026-07-01", "2026-09-30", 2_564_000_000, "X", fp="Q3"),  # quarter
            ]}
        }}},
    }
    filings = {"X": FilingMeta(date(2026, 9, 30), date(2026, 11, 1),
                               datetime(2026, 11, 1, 10, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(companyfacts, filings, {"X"}, fiscal_year_end="1231")
    assert res.rows[0].weighted_avg_diluted_shares == 2_564_000_000


def test_weighted_average_shares_falls_back_to_the_basic_and_diluted_concept():
    companyfacts = {
        "cik": 1326801,
        "facts": {"us-gaap": {"WeightedAverageNumberOfSharesOutstandingBasicAndDiluted": {
            "units": {"shares": [_dur("2026-07-01", "2026-09-30", 500_000, "X", fp="Q3")]}
        }}},
    }
    filings = {"X": FilingMeta(date(2026, 9, 30), date(2026, 11, 1),
                               datetime(2026, 11, 1, 10, tzinfo=UTC), "10-Q")}
    res = parse_snapshots(companyfacts, filings, {"X"}, fiscal_year_end="1231")
    assert res.rows[0].weighted_avg_diluted_shares == 500_000


# -- fiscal year end resolution (submissions.fiscalYearEnd is unreliable) -----

from app.services.sec_facts_parser import resolve_fiscal_year_end  # noqa: E402


def test_the_issuers_own_10k_overrides_a_wrong_declared_year_end():
    # Franklin Resources declares 1231 while every 10-K ends 09-30. Trusting the
    # declaration labelled its fiscal Q2 (Mar) as Q1, colliding with the real
    # fiscal Q1 (Dec) and destroying the quarter chain.
    filings = {
        "K": _meta("2025-09-30", "10-K"),
        "Q": _meta("2025-12-31"),
    }
    assert resolve_fiscal_year_end(filings, "1231") == "0930"


def test_declared_year_end_is_used_when_no_annual_filing_is_present():
    assert resolve_fiscal_year_end({"Q": _meta("2026-03-31")}, "1231") == "1231"
    assert resolve_fiscal_year_end({}, None) is None


def test_a_wrong_declared_year_end_no_longer_collides_two_periods():
    """End to end: BEN's Dec and Mar quarters must land on distinct keys."""
    def _q(accn, start, end, val):
        return _dur(start, end, val, accn, fp="Q1")

    companyfacts = {
        "cik": 38777,
        "facts": {"us-gaap": {"Revenues": {"units": {"USD": [
            _q("Q1", "2025-10-01", "2025-12-31", 2327),   # fiscal Q1
            _q("Q2", "2025-10-01", "2026-03-31", 4622),   # fiscal Q2 YTD
        ]}}}},
    }
    filings = {
        "K": _meta("2025-09-30", "10-K"),
        "Q1": _meta("2025-12-31"),
        "Q2": _meta("2026-03-31"),
    }
    res = parse_snapshots(companyfacts, filings, {"Q1", "Q2"}, fiscal_year_end="1231")
    keys = {(r.fiscal_year, r.fiscal_period) for r in res.rows}
    assert len(keys) == 2, f"periods collided on one key: {keys}"
    assert keys == {(2026, "Q1"), (2026, "Q2")}
