from __future__ import annotations

import json
from decimal import Decimal

import pytest

from jarvis_finance.imports.execution import execute_initial_snapshot_plan
from jarvis_finance.ledger.corrections import record_correction_audit, void_transaction
from jarvis_finance.ledger.positions import calculate_positions
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


def setup_conn():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute("INSERT INTO platforms(platform_id,name,platform_type,default_currency,created_at) VALUES('p1','True Wealth','broker','CHF','now')")
    conn.execute("INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,created_at) VALUES('a1','p1','Synthetic TW','brokerage','CHF','now')")
    conn.execute("INSERT INTO instruments(instrument_id,asset_class,name,ticker,isin,currency,created_at) VALUES('i1','ETF','Synthetic ETF','SETF','CH0000000001','USD','now')")
    conn.execute("""
        INSERT INTO broker_import_dry_runs(
            dry_run_id, source_platform, source_file_type, detected_sections_json, quality_flags_json, created_at
        ) VALUES('dry1','True Wealth','docx','{}','[]','now')
    """)
    conn.execute("""
        INSERT INTO broker_import_review_items(
            review_item_id, dry_run_id, source_platform, source_file_type, source_row_ref, row_hash,
            mapped_account_id, mapped_instrument_id, detected_quantity_present, quality_flags_json,
            review_status, import_readiness_status, reviewer_confirmed, snapshot_date_confirmed, ticker_exchange_confirmed, account_mapping_status, created_at
        ) VALUES('rev1','dry1','True Wealth','docx','row-1','hash-1','a1','i1',1,'[]','resolved','ready_for_import',1,1,1,'selected','now')
    """)
    payload = {
        "account_id": "a1",
        "instrument_id": "i1",
        "trade_date": "2025-12-31",
        "quantity": "1.234500",
        "currency_original": "USD",
        "fx_status": "missing",
    }
    conn.execute("""
        INSERT INTO broker_import_execution_plans(
            execution_plan_id, dry_run_id, review_item_id, source_platform, target_account_id, target_instrument_id,
            transaction_type, snapshot_date, payload_status, payload_quality_flags_json, source_row_hash,
            planned_write_summary_json, execution_status, created_at
        ) VALUES('plan1','dry1','rev1','True Wealth','a1','i1','initial_position_snapshot','2025-12-31','ready',?, 'hash-1', ?, 'planned','now')
    """, (json.dumps(["missing_fx", "cost_basis_uncertain", "snapshot_only"]), json.dumps(payload)))
    return conn


def test_execution_plan_import_blocks_same_execution_plan_id_and_row_hash() -> None:
    conn = setup_conn()
    result = execute_initial_snapshot_plan(conn, execution_plan_id="plan1", note="synthetic reviewed import")
    assert result.imported is True
    tx = conn.execute("SELECT * FROM transactions WHERE transaction_id=?", (result.transaction_id,)).fetchone()
    assert tx["source_id"] == "plan1"
    assert tx["row_hash"] == "hash-1"
    assert tx["quality_status"] == "warning"
    assert Decimal(str(tx["quantity"])) == Decimal("1.234500")

    with pytest.raises(ValueError, match="already imported"):
        execute_initial_snapshot_plan(conn, execution_plan_id="plan1", note="retry same plan")

    conn.execute("""
        INSERT INTO broker_import_review_items(
            review_item_id, dry_run_id, source_platform, source_file_type, source_row_ref, row_hash,
            mapped_account_id, mapped_instrument_id, detected_quantity_present, quality_flags_json,
            review_status, import_readiness_status, reviewer_confirmed, snapshot_date_confirmed, ticker_exchange_confirmed, account_mapping_status, created_at
        ) VALUES('rev2','dry1','True Wealth','docx','row-1-copy','hash-1','a1','i1',1,'[]','resolved','ready_for_import',1,1,1,'selected','now')
    """)
    conn.execute("INSERT INTO instruments(instrument_id,asset_class,name,ticker,isin,currency,created_at) VALUES('i2','ETF','Synthetic ETF 2','SET2','CH0000000002','USD','now')")
    conn.execute("""
        INSERT INTO broker_import_execution_plans(
            execution_plan_id, dry_run_id, review_item_id, source_platform, target_account_id, target_instrument_id,
            transaction_type, snapshot_date, payload_status, payload_quality_flags_json, source_row_hash,
            planned_write_summary_json, execution_status, created_at
        ) VALUES('plan2','dry1','rev2','True Wealth','a1','i2','initial_position_snapshot','2025-12-31','ready','[]','hash-1', ?, 'planned','now')
    """, (json.dumps({"account_id":"a1","instrument_id":"i2","trade_date":"2025-12-31","quantity":"2","currency_original":"USD","fx_status":"missing"}),))
    with pytest.raises(ValueError, match="row hash already imported|source row hash already imported|already imported"):
        execute_initial_snapshot_plan(conn, execution_plan_id="plan2", note="retry same row")


def test_void_transaction_requires_note_audits_and_excludes_from_position() -> None:
    conn = setup_conn()
    result = execute_initial_snapshot_plan(conn, execution_plan_id="plan1", note="synthetic reviewed import")
    assert calculate_positions(conn).positions[("a1", "i1")].quantity == Decimal("1.234500")

    with pytest.raises(ValueError, match="note"):
        void_transaction(conn, transaction_id=result.transaction_id, note="")

    audit_id = void_transaction(conn, transaction_id=result.transaction_id, note="wrong source row")
    assert audit_id
    assert conn.execute("SELECT is_voided FROM transactions WHERE transaction_id=?", (result.transaction_id,)).fetchone()["is_voided"] == 1
    assert conn.execute("SELECT COUNT(*) AS n FROM audit_log WHERE action='void_transaction'").fetchone()["n"] == 1
    assert conn.execute("SELECT COUNT(*) AS n FROM alerts WHERE rule_id='voided_transaction'").fetchone()["n"] == 1
    assert ("a1", "i1") not in calculate_positions(conn).positions


def test_correction_transaction_creates_audit_link() -> None:
    conn = setup_conn()
    result = execute_initial_snapshot_plan(conn, execution_plan_id="plan1", note="synthetic reviewed import")
    conn.execute("""
        INSERT INTO transactions(transaction_id, transaction_type, account_id, instrument_id, trade_date, quantity, currency_original, fx_status, source_type, is_confirmed, quality_status, notes, created_at)
        VALUES('tx-correction','initial_position_snapshot','a1','i1','2025-12-31','1.000000','USD','missing','manual_correction',1,'warning','synthetic correction','now')
    """)
    audit_id = record_correction_audit(conn, old_transaction_id=result.transaction_id, new_transaction_id="tx-correction", note="replace initial snapshot")
    assert audit_id
    row = conn.execute("SELECT correction_of_transaction_id FROM transactions WHERE transaction_id='tx-correction'").fetchone()
    assert row["correction_of_transaction_id"] == result.transaction_id
    assert conn.execute("SELECT COUNT(*) AS n FROM audit_log WHERE action='correction_transaction'").fetchone()["n"] == 1


def test_missing_fx_initial_snapshot_stays_incomplete_without_fake_total_return() -> None:
    conn = setup_conn()
    execute_initial_snapshot_plan(conn, execution_plan_id="plan1", note="synthetic reviewed import")
    pos = calculate_positions(conn).positions[("a1", "i1")]
    assert pos.data_quality_status == "incomplete"
    assert "missing_fx" in pos.quality_warnings
    assert "missing_market_price" in pos.quality_warnings
    assert pos.total_return_chf is None
