"""Tests for CIK/SIC resolution (read-only), apply-in-promote, and the
composite-revision fingerprint."""

from __future__ import annotations

import os
import tempfile

import pytest
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

from app.database import Base
import app.models  # noqa: F401
from app.models.ticker import Ticker
from app.services import sec_universe as su


@pytest.fixture
async def factory():
    fd, path = tempfile.mkstemp(suffix=".db")
    os.close(fd)
    eng = create_async_engine(f"sqlite+aiosqlite:///{path}")
    async with eng.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    try:
        yield async_sessionmaker(eng, class_=AsyncSession, expire_on_commit=False)
    finally:
        await eng.dispose()
        try:
            os.unlink(path)
        except OSError:
            pass


class FakeSecClient:
    def __init__(self, tickers, submissions=None):
        self._tickers = tickers
        self._submissions = submissions or {}

    async def company_tickers(self):
        return dict(self._tickers)

    async def submissions(self, cik, *, include_history=False):
        return self._submissions[int(cik)]


async def test_resolve_ciks_is_read_only_and_proposes_updates(factory):
    async with factory() as s:
        for sym in ["AAPL", "GOOGL", "GOOG", "ZZZZ"]:  # ZZZZ not in SEC
            s.add(Ticker(symbol=sym))
        await s.commit()

    client = FakeSecClient({"AAPL": 320193, "GOOGL": 1652044, "GOOG": 1652044})
    async with factory() as s:
        resolved = await su.resolve_ciks(s, client)
        assert not s.dirty and not s.new  # NOTHING mutated during resolution
        await s.rollback()

    assert resolved.symbol_to_cik == {"AAPL": 320193, "GOOGL": 1652044, "GOOG": 1652044}
    assert len(resolved.cik_updates) == 3  # AAPL, GOOGL, GOOG (ZZZZ unresolved)
    assert set(resolved.cik_to_ticker_ids) == {320193, 1652044}

    # Read-only really means the DB is untouched until apply.
    async with factory() as s:
        ciks = {t.symbol: t.cik for t in (await s.execute(select(Ticker))).scalars()}
    assert all(v is None for v in ciks.values())


async def test_apply_ticker_updates_writes_cik_and_sic(factory):
    async with factory() as s:
        for sym in ["GOOGL", "GOOG"]:
            s.add(Ticker(symbol=sym))
        await s.commit()

    client = FakeSecClient(
        {"GOOGL": 1652044, "GOOG": 1652044},
        submissions={1652044: {"sic": "7370", "sic_description": "Services-Computer"}},
    )
    async with factory() as s:
        resolved = await su.resolve_ciks(s, client)
        sic_updates = await su.fetch_sic_updates(client, resolved.cik_to_ticker_ids)
        counts = await su.apply_ticker_updates(s, resolved, sic_updates)
        await s.commit()

    assert counts == {"cik_updates": 2, "sic_updates": 2}
    async with factory() as s:
        rows = {t.symbol: (t.cik, t.sic, t.sic_description) for t in (await s.execute(select(Ticker))).scalars()}
    assert rows["GOOGL"] == ("0001652044", "7370", "Services-Computer")
    assert rows["GOOG"] == ("0001652044", "7370", "Services-Computer")


async def test_fetch_sic_updates_is_read_only(factory):
    client = FakeSecClient({}, submissions={1: {"sic": "1", "sic_description": "x"}})
    updates = await su.fetch_sic_updates(client, {1: [10, 11]})
    assert updates == [(10, "1", "x"), (11, "1", "x")]  # proposals only, no DB touched


def test_universe_fingerprint_changes_on_membership():
    a = su.universe_fingerprint({"AAPL": 320193, "MSFT": 789019})
    same = su.universe_fingerprint({"MSFT": 789019, "AAPL": 320193})  # order-independent
    added = su.universe_fingerprint({"AAPL": 320193, "MSFT": 789019, "NVDA": 1045810})
    remapped = su.universe_fingerprint({"AAPL": 999, "MSFT": 789019})
    assert a == same
    assert a != added  # new ticker forces a new revision
    assert a != remapped  # changed CIK mapping forces a new revision


def test_compose_revision_rejects_missing_index_date():
    with pytest.raises(ValueError):
        su.compose_revision(None, "abc", {"AAPL": 320193})


def test_compose_revision_and_index_hash():
    rows = [{"cik": 320193, "accession": "a-1"}, {"cik": 66740, "accession": "b-2"}]
    h1 = su.index_content_hash(rows)
    h2 = su.index_content_hash(list(reversed(rows)))
    assert h1 == h2  # order-independent
    rev = su.compose_revision("2026-07-21", h1, {"AAPL": 320193})
    assert rev.startswith("2026-07-21:") and rev.count(":") == 2
