from __future__ import annotations

__test__ = False  # Executed by the fixed-count portfolio integration gate.

import json
from datetime import date
from decimal import Decimal

from jarvis_finance.fx.providers import FrankfurterFxProvider
from jarvis_finance.fx.rates import resolve_fx_rate_to_chf
from jarvis_finance.market_data.prices import EquityPriceQuote, FmpEquityPriceProvider
from jarvis_finance.services.portfolio_analytics import _business_day_age, run_daily_market_valuation
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations
from test_portfolio_market_analytics_v1 import Fx, database


class FakeResponse:
    def __init__(self, payload: object) -> None:
        self.payload = payload

    def __enter__(self):
        return self

    def __exit__(self, *args):
        return False

    def read(self) -> bytes:
        return json.dumps(self.payload).encode("utf-8")


def test_frankfurter_sends_transparent_user_agent_and_persists_requested_direction_date(monkeypatch) -> None:
    captured = {}

    def fake_urlopen(req, *, timeout):
        captured["url"] = req.full_url
        captured["headers"] = dict(req.header_items())
        captured["timeout"] = timeout
        return FakeResponse([{"date": "2026-07-24", "base": "EUR", "quote": "CHF", "rate": "0.9321"}])

    monkeypatch.setattr("jarvis_finance.fx.providers.request.urlopen", fake_urlopen)
    conn = connect_memory()
    apply_migrations(conn)

    result = resolve_fx_rate_to_chf(
        conn,
        base_currency="EUR",
        rate_date="2026-07-24",
        providers=[FrankfurterFxProvider()],
        resolve_fixed=False,
    )

    assert result.rate == Decimal("0.9321")
    assert captured["headers"]["User-agent"] == FrankfurterFxProvider.user_agent
    assert "base=EUR" in captured["url"] and "quotes=CHF" in captured["url"]
    row = conn.execute(
        "SELECT base_currency, quote_currency, rate_date, rate, provider FROM fx_rates"
    ).fetchone()
    assert tuple(row) == ("EUR", "CHF", "2026-07-24", "0.9321", "frankfurter")


class HistoricalFmp(FmpEquityPriceProvider):
    def __init__(self, payload: object) -> None:
        super().__init__(api_key="synthetic")
        self.payload = payload
        self.calls: list[tuple[str, dict[str, str]]] = []
        self.returned_currencies: list[str] = []

    def _get_json(self, path: str, params: dict[str, str]) -> object:
        self.calls.append((path, params))
        if path == "/stable/profile":
            raise AssertionError("historical price requests must not use the current profile endpoint")
        return self.payload

    def get_price(self, provider_symbol: str, *, price_date: str | None = None) -> EquityPriceQuote:
        quote = super().get_price(provider_symbol, price_date=price_date)
        self.returned_currencies.append(quote.currency)
        return quote


def test_fmp_historical_price_uses_exact_requested_date_without_profile() -> None:
    provider = HistoricalFmp([
        {"date": "2026-07-01", "close": "251.34", "currency": "USD"},
        {"date": "2026-06-30", "close": "249.00", "currency": "USD"},
    ])

    quote = provider.get_price("vti", price_date="2026-07-01")

    assert quote.close == Decimal("251.34")
    assert quote.price_timestamp == "2026-07-01"
    assert provider.calls == [
        (
            "/stable/historical-price-eod/full",
            {"symbol": "VTI", "from": "2026-06-24", "to": "2026-07-01"},
        )
    ]


def test_fmp_historical_price_uses_latest_prior_trading_day_and_rejects_future_rows() -> None:
    provider = HistoricalFmp([
        {"date": "2026-07-06", "close": "999.00", "currency": "USD"},
        {"date": "2026-07-03", "close": "250.00", "currency": "USD"},
        {"date": "2026-06-26", "close": "240.00", "currency": "USD"},
    ])

    quote = provider.get_price("VTI", price_date="2026-07-05")

    assert quote.close == Decimal("250.00")
    assert quote.price_timestamp == "2026-07-03"


def test_fmp_historical_price_returns_missing_when_no_eligible_history() -> None:
    future_only = HistoricalFmp([{"date": "2026-07-02", "close": "999.00", "currency": "USD"}])
    outside_boundary = HistoricalFmp([{"date": "2026-06-23", "close": "200.00", "currency": "USD"}])
    empty = HistoricalFmp([])

    for provider in (future_only, outside_boundary, empty):
        quote = provider.get_price("VTI", price_date="2026-07-01")
        assert quote.close is None
        assert quote.quality_status == "missing"
        assert quote.error_message == "price_missing"


def test_fmp_historical_price_without_currency_uses_confirmed_mapping_currency(tmp_path) -> None:
    conn = database(policy=False)
    keep = conn.execute(
        """SELECT m.instrument_id FROM instrument_price_mappings m
           JOIN transactions t ON t.instrument_id=m.instrument_id
           JOIN instruments i ON i.instrument_id=m.instrument_id
           WHERE t.is_confirmed=1 AND t.is_voided=0 AND i.trading_currency='EUR'
           ORDER BY m.mapping_id LIMIT 1"""
    ).fetchone()[0]
    conn.execute("UPDATE instruments SET is_active=0 WHERE instrument_id<>?", (keep,))
    conn.execute("UPDATE instrument_price_mappings SET trading_currency='EUR', currency='EUR' WHERE instrument_id=?", (keep,))
    conn.execute("DELETE FROM market_data_runs")
    conn.commit()
    provider = HistoricalFmp([{"date": "2026-07-01", "close": "251.34"}])
    direct_quote = provider.get_price("SYNTHETIC", price_date="2026-07-01")
    assert direct_quote.currency == ""
    mapping_state = conn.execute(
        "SELECT provider,upper(COALESCE(trading_currency,currency,'')) FROM instrument_price_mappings WHERE instrument_id=?",
        (keep,),
    ).fetchone()
    assert tuple(mapping_state) == ("mock", "EUR")

    result = run_daily_market_valuation(
        conn,
        as_of="2026-07-01",
        price_providers={"mock": provider},
        fx_provider=Fx({("EUR", "2026-07-01"): "0.93"}),
        lock_path=tmp_path / "market.lock",
    )

    assert result.idempotent is False
    assert provider.returned_currencies == ["", ""]
    assert result.price_stored == result.price_total
    rows = conn.execute("SELECT currency,close,price_date FROM market_prices").fetchall()
    assert rows and {tuple(row) for row in rows} == {("EUR", "251.34", "2026-07-01")}


def test_daily_orchestrator_reports_price_missing_and_rerun_is_idempotent(tmp_path) -> None:
    conn = database(policy=False)
    conn.execute("UPDATE instrument_price_mappings SET provider='fmp'")
    conn.commit()
    provider = HistoricalFmp([])

    first = run_daily_market_valuation(
        conn,
        as_of="2026-07-01",
        price_providers={"fmp": provider},
        fx_provider=Fx({}),
        lock_path=tmp_path / "market.lock",
    )
    replay = run_daily_market_valuation(
        conn,
        as_of="2026-07-01",
        price_providers={"fmp": provider},
        fx_provider=Fx({}),
        lock_path=tmp_path / "market.lock",
    )

    assert first.status == "partial"
    assert {item["reason_code"] for item in first.missing_instruments} == {"price_missing"}
    assert replay.idempotent is False and replay.run_id == first.run_id
    assert replay.status == "partial"
    assert conn.execute("SELECT COUNT(*) FROM market_prices").fetchone()[0] == 0
    assert all(path != "/stable/profile" for path, _ in provider.calls)


def test_daily_orchestrator_never_persists_future_price(tmp_path) -> None:
    class FutureProvider:
        name = "mock"

        def get_price(self, provider_symbol: str, *, price_date: str | None = None) -> EquityPriceQuote:
            return EquityPriceQuote(
                provider_symbol=provider_symbol,
                currency="CHF",
                close=Decimal("100"),
                provider=self.name,
                price_timestamp="2026-07-02T20:00:00+00:00",
            )

    conn = database(policy=False)
    run_daily_market_valuation(
        conn,
        as_of="2026-07-01",
        price_providers={"mock": FutureProvider()},
        fx_provider=Fx({}),
        lock_path=tmp_path / "market.lock",
    )

    assert _business_day_age(date(2026, 7, 2), date(2026, 7, 1)) < 0
    assert conn.execute("SELECT COUNT(*) FROM market_prices").fetchone()[0] == 0


def run_sprint9_fx_fmp_history_contract(tmp_path, monkeypatch) -> None:
    test_frankfurter_sends_transparent_user_agent_and_persists_requested_direction_date(monkeypatch)
    test_fmp_historical_price_uses_exact_requested_date_without_profile()
    test_fmp_historical_price_uses_latest_prior_trading_day_and_rejects_future_rows()
    test_fmp_historical_price_returns_missing_when_no_eligible_history()
    test_fmp_historical_price_without_currency_uses_confirmed_mapping_currency(tmp_path / "currency")
    test_daily_orchestrator_reports_price_missing_and_rerun_is_idempotent(tmp_path / "missing")
    test_daily_orchestrator_never_persists_future_price(tmp_path / "future")
