from __future__ import annotations

from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.main import create_app
from jarvis_finance.api.schemas.performance_activation import (
    PerformanceSourceActivationPreviewResponse,
)
from jarvis_finance.cli.main import main as cli_main
from jarvis_finance.services.daily_valuations import run_daily_crypto_valuation
from jarvis_finance.services.performance_activation import (
    build_daily_valuation_job_status,
    confirm_performance_backfill,
    confirm_performance_source_activation,
    preview_performance_backfill,
    preview_performance_source_activation,
)
from jarvis_finance.services.performance_scope import (
    set_performance_cashflow_coverage,
    set_performance_scope_classification,
)
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import MIGRATION_VERSION, apply_migrations

NOW = "2026-01-01T12:00:00+00:00"


def crypto_db(*, with_scope: bool = True):
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,created_at) VALUES('crypto-p','Synthetic Crypto','crypto',?)",
        (NOW,),
    )
    conn.execute(
        """INSERT INTO accounts(
               account_id,platform_id,account_name,account_type,currency,created_at
           ) VALUES('crypto-a','crypto-p','Crypto Performance','brokerage','CHF',?)""",
        (NOW,),
    )
    if with_scope:
        set_performance_scope_classification(
            conn,
            account_id="crypto-a",
            included=True,
            classification_role="crypto_portfolio",
            source="synthetic_activation",
            note="Synthetic crypto performance scope",
            classified_at=NOW,
        )
        set_performance_cashflow_coverage(
            conn,
            account_id="crypto-a",
            coverage_from="2026-01-01",
            coverage_to="2026-12-31",
            status="complete",
            source="confirmed_snapshot_start_v1",
            note="Synthetic confirmed start balance and complete flows",
            recorded_at=NOW,
        )
    conn.execute(
        """INSERT INTO crypto_assets(
               asset_id,coin_name,symbol,coingecko_id,created_at
           ) VALUES('btc','Bitcoin','BTC','bitcoin',?)""",
        (NOW,),
    )
    conn.execute(
        "INSERT INTO crypto_wallets(wallet_id,wallet_name,wallet_type,created_at) VALUES('w','Synthetic','exchange',?)",
        (NOW,),
    )
    conn.execute(
        """INSERT INTO crypto_holdings(
               crypto_holding_id,asset_id,wallet_id,quantity,acquisition_source,
               last_verified_at,verification_status,created_at
           ) VALUES('h','btc','w','2','initial_snapshot',?,'verified',?)""",
        (NOW, NOW),
    )
    conn.commit()
    return conn


def add_price(conn, day: str, price: str = "100") -> None:
    conn.execute(
        """INSERT INTO crypto_prices(
               crypto_price_id,asset_id,coingecko_id,price_currency,price,provider,
               provider_timestamp,fetched_at,quality_status
           ) VALUES(?,?,?,?,?,?,?,?,?)""",
        (f"price-{day}", "btc", "bitcoin", "CHF", price, "synthetic", f"{day}T20:00:00+00:00", f"{day}T20:01:00+00:00", "fresh"),
    )
    conn.commit()


def test_crypto_daily_valuation_is_canonical_and_idempotent(tmp_path: Path) -> None:
    conn = crypto_db()
    add_price(conn, "2026-01-01")

    first = run_daily_crypto_valuation(conn, as_of="2026-01-01", lock_path=tmp_path / "crypto.lock")
    second = run_daily_crypto_valuation(conn, as_of="2026-01-01", lock_path=tmp_path / "crypto.lock")

    assert first.status == "complete" and first.valuation_stored == 1
    assert second.idempotent is True and second.run_id == first.run_id
    row = conn.execute(
        """SELECT value_original,valuation_at,quality_status,source_reference
           FROM portfolio_valuation_snapshots WHERE scope_id='crypto-a'"""
    ).fetchone()
    assert tuple(row)[:3] == ("200", "2026-01-01", "complete")
    assert first.run_id in row["source_reference"]
    assert conn.execute(
        "SELECT COUNT(*) FROM portfolio_valuation_snapshots WHERE scope_id='crypto-a' AND substr(valuation_at,1,10)='2026-01-01'"
    ).fetchone()[0] == 1


def test_pending_crypto_transaction_never_changes_confirmed_daily_valuation(tmp_path: Path) -> None:
    conn = crypto_db()
    add_price(conn, "2026-01-02", "120")
    conn.execute(
        """INSERT INTO crypto_transactions(
               crypto_transaction_id,transaction_type,asset_id,quantity,to_wallet_id,
               transaction_datetime,source,confirmation_status,created_at
           ) VALUES('pending-buy','buy','btc','10','w','2026-01-02T10:00:00+00:00',
                    'synthetic','pending',?)""",
        (NOW,),
    )
    conn.commit()
    result = run_daily_crypto_valuation(
        conn, as_of="2026-01-02", lock_path=tmp_path / "crypto.lock"
    )
    value = conn.execute(
        "SELECT value_original FROM portfolio_valuation_snapshots WHERE scope_id='crypto-a'"
    ).fetchone()[0]
    assert result.status == "complete"
    assert value == "240"


def test_crypto_price_correction_appends_explicit_supersession_lineage(tmp_path: Path) -> None:
    conn = crypto_db()
    add_price(conn, "2026-01-01", "100")
    run_daily_crypto_valuation(conn, as_of="2026-01-01", lock_path=tmp_path / "crypto.lock")
    first_id = conn.execute(
        "SELECT snapshot_id FROM portfolio_valuation_snapshots WHERE scope_id='crypto-a'"
    ).fetchone()[0]
    conn.execute("UPDATE crypto_prices SET price='120' WHERE crypto_price_id='price-2026-01-01'")
    conn.commit()

    corrected = run_daily_crypto_valuation(
        conn, as_of="2026-01-01", lock_path=tmp_path / "crypto.lock"
    )
    rows = conn.execute(
        """SELECT snapshot_version,value_original,supersedes_snapshot_id
           FROM portfolio_valuation_snapshots WHERE scope_id='crypto-a'
           ORDER BY snapshot_version"""
    ).fetchall()

    assert corrected.valuation_stored == 1
    assert [tuple(row) for row in rows] == [(1, "200", None), (2, "240", first_id)]


def test_missing_crypto_provider_day_never_overwrites_last_confirmed_value(tmp_path: Path) -> None:
    conn = crypto_db()
    add_price(conn, "2026-01-01")
    run_daily_crypto_valuation(conn, as_of="2026-01-01", lock_path=tmp_path / "crypto.lock")

    failed = run_daily_crypto_valuation(conn, as_of="2026-01-02", lock_path=tmp_path / "crypto.lock")

    assert failed.status == "partial" and failed.valuation_stored == 0
    assert "crypto_exact_date_price_missing" in failed.reason_codes
    assert conn.execute(
        "SELECT MAX(substr(valuation_at,1,10)) FROM portfolio_valuation_snapshots WHERE scope_id='crypto-a'"
    ).fetchone()[0] == "2026-01-01"


def test_backfill_preview_is_read_only_and_confirm_is_audited_and_idempotent(tmp_path: Path) -> None:
    conn = crypto_db()
    add_price(conn, "2026-01-01")
    before = conn.total_changes

    preview = preview_performance_backfill(
        conn,
        source="crypto",
        period_from="2026-01-01",
        period_to="2026-01-01",
    )

    assert conn.total_changes == before
    assert preview["can_confirm"] is True and preview["days_to_materialize"] == 1
    request = {
        "source": "crypto",
        "period_from": "2026-01-01",
        "period_to": "2026-01-01",
        "preview_id": preview["preview_id"],
        "input_fingerprint": preview["input_fingerprint"],
        "confirmation_id": "synthetic-confirmation-1",
        "confirm": True,
        "note": "Synthetic explicit confirmation",
    }
    first = confirm_performance_backfill(conn, request, lock_dir=tmp_path)
    second = confirm_performance_backfill(conn, request, lock_dir=tmp_path)

    assert first["idempotent"] is False and len(first["runs"]) == 1
    assert second == {
        "confirmation_id": "synthetic-confirmation-1",
        "source": "crypto",
        "idempotent": True,
        "runs": [],
        "audit_id": first["audit_id"],
        "completed": True,
        "remaining_days": 0,
    }
    assert conn.execute(
        "SELECT COUNT(*) FROM audit_log WHERE action='performance_backfill_confirmed' AND entity_id='synthetic-confirmation-1'"
    ).fetchone()[0] == 1


def test_truewealth_backfill_refuses_synthetic_carry_forward() -> None:
    conn = crypto_db()
    preview = preview_performance_backfill(
        conn,
        source="truewealth",
        period_from="2026-01-01",
        period_to="2026-01-02",
    )
    assert preview["can_confirm"] is False
    assert "truewealth_requires_new_confirmed_values" in preview["blockers"]


def test_partial_backfill_rejects_input_drift_and_new_preview_can_complete(tmp_path: Path) -> None:
    conn = crypto_db()
    preview = preview_performance_backfill(
        conn, source="crypto", period_from="2026-01-01", period_to="2026-01-01"
    )
    request = {
        "source": "crypto",
        "period_from": "2026-01-01",
        "period_to": "2026-01-01",
        "preview_id": preview["preview_id"],
        "input_fingerprint": preview["input_fingerprint"],
        "confirmation_id": "synthetic-resumable-confirmation",
        "confirm": True,
        "note": "Synthetic resumable confirmation",
    }
    first = confirm_performance_backfill(conn, request, lock_dir=tmp_path)
    assert first["completed"] is False and first["remaining_days"] == 1
    assert conn.execute(
        "SELECT COUNT(*) FROM audit_log WHERE action='performance_backfill_confirmed'"
    ).fetchone()[0] == 0

    add_price(conn, "2026-01-01")
    updated = preview_performance_backfill(
        conn, source="crypto", period_from="2026-01-01", period_to="2026-01-01"
    )
    assert updated["input_fingerprint"] != preview["input_fingerprint"]
    with pytest.raises(ValueError, match="inputs changed"):
        confirm_performance_backfill(conn, request, lock_dir=tmp_path)
    new_request = {
        **request,
        "preview_id": updated["preview_id"],
        "input_fingerprint": updated["input_fingerprint"],
        "confirmation_id": "synthetic-resumable-confirmation-v2",
    }
    resumed = confirm_performance_backfill(conn, new_request, lock_dir=tmp_path)
    repeated = confirm_performance_backfill(conn, new_request, lock_dir=tmp_path)
    assert resumed["completed"] is True and resumed["remaining_days"] == 0
    assert repeated["idempotent"] is True and repeated["completed"] is True


def test_daily_job_endpoint_is_read_only_and_reports_explicit_activation(monkeypatch) -> None:
    conn = crypto_db()
    monkeypatch.delenv("JARVIS_FINANCE_DAILY_VALUATION_ENABLED", raising=False)
    app = create_app()
    app.dependency_overrides[get_db] = lambda: conn
    before = conn.total_changes

    response = TestClient(app).get("/api/portfolio/performance/daily-job")

    assert response.status_code == 200
    assert response.json()["activation_required"] is True
    assert response.json()["enabled"] is False
    assert conn.total_changes == before


def test_job_status_preserves_last_confirmed_date_when_latest_run_is_partial() -> None:
    conn = crypto_db()
    conn.execute(
        """INSERT INTO market_data_runs(
               run_id,source_key,as_of,input_fingerprint,status,started_at,completed_at,reason_codes_json
           ) VALUES('complete-run','daily_crypto_valuation_v1','2026-01-01',?,'complete',?,?,'[]')""",
        ("a" * 64, "2026-01-01T20:00:00+00:00", "2026-01-01T20:01:00+00:00"),
    )
    conn.execute(
        """INSERT INTO market_data_runs(
               run_id,source_key,as_of,input_fingerprint,status,started_at,completed_at,reason_codes_json
           ) VALUES('partial-run','daily_crypto_valuation_v1','2026-01-02',?,'partial',?,?,?)""",
        ("b" * 64, "2026-01-02T20:00:00+00:00", "2026-01-02T20:01:00+00:00", '["provider_failed"]'),
    )
    conn.commit()
    source = next(
        item for item in build_daily_valuation_job_status(conn)["sources"]
        if item["source_key"] == "daily_crypto_valuation_v1"
    )
    assert source["status"] == "partial"
    assert source["last_confirmed_date"] == "2026-01-01"


def test_scoped_performance_api_uses_only_the_requested_source(tmp_path: Path) -> None:
    conn = crypto_db()
    add_price(conn, "2026-01-01", "100")
    add_price(conn, "2026-01-02", "120")
    run_daily_crypto_valuation(conn, as_of="2026-01-01", lock_path=tmp_path / "crypto.lock")
    run_daily_crypto_valuation(conn, as_of="2026-01-02", lock_path=tmp_path / "crypto.lock")
    app = create_app()
    app.dependency_overrides[get_db] = lambda: conn

    response = TestClient(app).get(
        "/api/portfolio/performance?from=2026-01-01&to=2026-01-02&method=both&scope=crypto"
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["summary"]["opening_value"] == "200"
    assert payload["summary"]["closing_value"] == "240"
    assert [row["value"] for row in payload["time_series"]] == ["200", "240"]


def test_coverage_endpoint_rejects_an_incomplete_date_pair() -> None:
    conn = crypto_db()
    app = create_app()
    app.dependency_overrides[get_db] = lambda: conn
    response = TestClient(app).get("/api/portfolio/performance/coverage?from=2026-01-01")
    assert response.status_code == 400


def test_daily_cli_is_fail_closed_without_touching_runtime(monkeypatch, capsys) -> None:
    monkeypatch.delenv("JARVIS_FINANCE_DAILY_VALUATION_ENABLED", raising=False)
    assert cli_main(["run-daily-market-valuation"]) == 0
    assert "status=activation_required" in capsys.readouterr().out


def test_source_activation_preview_confirm_is_financially_read_only_audited_and_idempotent() -> None:
    conn = crypto_db(with_scope=False)
    before = conn.total_changes
    arguments = {
        "source": "crypto",
        "tracking_mode": "confirmed_start_snapshot",
        "period_from": "2026-01-01",
        "period_to": "2026-12-31",
        "evidence_reference": "synthetic verified start balance",
        "confirmed_start_date": "2026-01-01",
        "attest_complete_external_flows": True,
    }
    preview = preview_performance_source_activation(conn, **arguments)
    assert conn.total_changes == before
    PerformanceSourceActivationPreviewResponse.model_validate(preview)
    assert preview["can_confirm"] is True
    assert preview["planned_changes"]["financial_snapshots"] == 0
    request = {
        **arguments,
        "preview_id": preview["preview_id"],
        "input_fingerprint": preview["input_fingerprint"],
        "confirmation_id": "synthetic-crypto-activation-1",
        "confirm": True,
    }
    first = confirm_performance_source_activation(conn, request)
    second = confirm_performance_source_activation(conn, request)
    assert first["idempotent"] is False and second["idempotent"] is True
    assert conn.execute("SELECT COUNT(*) FROM portfolio_valuation_snapshots").fetchone()[0] == 0
    scope = conn.execute("SELECT classification_role,included FROM performance_scope_classifications").fetchone()
    coverage = conn.execute("SELECT coverage_from,status,source FROM performance_cashflow_coverage").fetchone()
    assert tuple(scope) == ("crypto_portfolio", 1)
    assert tuple(coverage) == ("2026-01-01", "complete", "confirmed_start_snapshot_v1")
    assert conn.execute(
        "SELECT COUNT(*) FROM audit_log WHERE action='performance_source_activation_confirmed'"
    ).fetchone()[0] == 1


def test_source_activation_rejects_changed_confirmed_holdings_after_preview() -> None:
    conn = crypto_db(with_scope=False)
    arguments = {
        "source": "crypto",
        "tracking_mode": "confirmed_start_snapshot",
        "period_from": "2026-01-01",
        "period_to": "2026-12-31",
        "evidence_reference": "synthetic verified start balance",
        "confirmed_start_date": "2026-01-01",
        "attest_complete_external_flows": True,
    }
    preview = preview_performance_source_activation(conn, **arguments)
    conn.execute("UPDATE crypto_holdings SET quantity='3' WHERE crypto_holding_id='h'")
    conn.commit()
    with pytest.raises(ValueError, match="stale"):
        confirm_performance_source_activation(
            conn,
            {
                **arguments,
                "preview_id": preview["preview_id"],
                "input_fingerprint": preview["input_fingerprint"],
                "confirmation_id": "synthetic-stale-activation",
                "confirm": True,
            },
        )


def test_schema_remains_49() -> None:
    assert MIGRATION_VERSION == 53
