from __future__ import annotations

from decimal import Decimal

from jarvis_finance.ledger.cash import apply_manual_cash_correction, calculate_cash_balances
from jarvis_finance.ledger.positions import calculate_positions, save_position_snapshots
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','Demo Broker','broker','CHF','now')")
    conn.execute("INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,created_at) VALUES('a1','p1','Main','brokerage','CHF','now')")
    conn.execute("INSERT INTO instruments(instrument_id,asset_class,name,ticker,isin,currency,created_at) VALUES('i1','equity','Demo AG','DAG','CH0001','CHF','now')")
    conn.execute("INSERT INTO instruments(instrument_id,asset_class,name,ticker,isin,currency,created_at) VALUES('u1','equity','USD Demo Inc','UDI','US0001','USD','now')")
    return conn


def tx(conn, tid, ttype, *, instrument='i1', date='2026-01-01', qty=None, gross=None, fee=0, tax=0, net=None, currency='CHF', fx=1, note='synthetic'):
    conn.execute(
        """INSERT INTO transactions(
            transaction_id, transaction_type, account_id, instrument_id, trade_date,
            quantity, price_original, gross_amount_original, fee_original, tax_original,
            net_amount_original, currency_original, fx_rate_to_chf, fx_status,
            gross_amount_chf, fee_chf, tax_chf, net_amount_chf,
            source_type, is_confirmed, quality_status, notes, created_at
        ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
        (
            tid, ttype, 'a1', instrument, date, str(qty) if qty is not None else None, None,
            str(gross) if gross is not None else None, str(fee), str(tax), str(net) if net is not None else None,
            currency, str(fx) if fx is not None else None, 'ok' if fx is not None else 'missing',
            str(Decimal(str(gross))*Decimal(str(fx))) if gross is not None and fx is not None else None,
            str(Decimal(str(fee))*Decimal(str(fx))) if fx is not None else None,
            str(Decimal(str(tax))*Decimal(str(fx))) if fx is not None else None,
            str(Decimal(str(net))*Decimal(str(fx))) if net is not None and fx is not None else None,
            'synthetic', 1, 'ok' if fx is not None else 'incomplete', note, 'now'
        ),
    )


def test_initial_cash_snapshot_is_start_truth_and_buy_sell_dividend_fee_tax_move_cash() -> None:
    conn = setup_conn()
    tx(conn, 'c0', 'initial_cash_snapshot', instrument=None, gross=1000, net=1000)
    tx(conn, 'b1', 'buy', qty=5, gross=500, fee=5, net=505)
    tx(conn, 's1', 'partial_sell', qty=2, gross=260, fee=2, tax=1, net=257)
    tx(conn, 'd1', 'dividend', qty=None, gross=20, tax=7, net=13)
    tx(conn, 'f1', 'fee', instrument=None, gross=3, net=3)
    tx(conn, 't1', 'tax', instrument=None, gross=4, net=4)

    result = calculate_cash_balances(conn)

    key = ('a1', 'CHF')
    assert result.balances[key].amount_original == Decimal('758')
    assert result.balances[key].amount_chf == Decimal('758')
    assert result.negative_cash_warnings == []


def test_cash_negative_creates_warning_not_correction() -> None:
    conn = setup_conn()
    tx(conn, 'c0', 'initial_cash_snapshot', instrument=None, gross=100, net=100)
    tx(conn, 'b1', 'buy', qty=2, gross=200, fee=0, net=200)

    result = calculate_cash_balances(conn)

    assert result.balances[('a1', 'CHF')].amount_original == Decimal('-100')
    assert result.negative_cash_warnings
    alert = conn.execute("SELECT priority, rule_id FROM alerts WHERE rule_id='negative_cash'").fetchone()
    assert alert['priority'] == 'warnung'


def test_manual_cash_correction_requires_note_and_audit() -> None:
    conn = setup_conn()
    try:
        apply_manual_cash_correction(conn, account_id='a1', currency='CHF', amount=Decimal('10'), note='')
    except ValueError as exc:
        assert 'note' in str(exc)
    else:
        raise AssertionError('expected note validation')

    audit_id = apply_manual_cash_correction(conn, account_id='a1', currency='CHF', amount=Decimal('10'), note='Synthetic reconciliation')
    assert audit_id
    assert conn.execute("SELECT COUNT(*) AS n FROM audit_log WHERE action='manual_cash_correction'").fetchone()['n'] == 1


def test_weighted_average_cost_buy_fee_usd_fx_and_partial_sell_realized_pnl() -> None:
    conn = setup_conn()
    tx(conn, 'b1', 'buy', instrument='u1', date='2026-01-01', qty=10, gross=1000, fee=10, net=1010, currency='USD', fx='0.90')
    tx(conn, 'b2', 'buy', instrument='u1', date='2026-01-02', qty=10, gross=1200, fee=12, net=1212, currency='USD', fx='0.80')
    tx(conn, 's1', 'partial_sell', instrument='u1', date='2026-01-03', qty=5, gross=700, fee=7, net=693, currency='USD', fx='0.85')

    positions = calculate_positions(conn)
    pos = positions.positions[('a1', 'u1')]

    assert pos.quantity == Decimal('15')
    assert pos.cost_basis_original == Decimal('1666.5')
    assert pos.cost_basis_chf == Decimal('1408.95')
    assert pos.average_cost_original == Decimal('111.1')
    assert pos.realized_pnl_chf == Decimal('119.40')
    assert pos.fees_chf == Decimal('24.55')


def test_full_sell_sets_quantity_zero_and_keeps_historical_position_visible() -> None:
    conn = setup_conn()
    tx(conn, 'b1', 'buy', qty=3, gross=300, fee=0, net=300)
    tx(conn, 's1', 'full_sell', qty=3, gross=330, fee=0, net=330)

    pos = calculate_positions(conn).positions[('a1', 'i1')]

    assert pos.quantity == Decimal('0')
    assert pos.cost_basis_chf == Decimal('0')
    assert pos.realized_pnl_chf == Decimal('30')


def test_dividend_changes_income_not_quantity_or_cost_basis() -> None:
    conn = setup_conn()
    tx(conn, 'b1', 'buy', qty=4, gross=400, fee=4, net=404)
    tx(conn, 'd1', 'dividend', qty=None, gross=40, tax=14, net=26)

    pos = calculate_positions(conn).positions[('a1', 'i1')]

    assert pos.quantity == Decimal('4')
    assert pos.cost_basis_chf == Decimal('404')
    assert pos.income_chf == Decimal('26')
    assert pos.taxes_chf == Decimal('14')


def test_missing_market_price_warns_and_missing_fx_blocks_precise_total_return() -> None:
    conn = setup_conn()
    tx(conn, 'b1', 'buy', qty=2, gross=200, fee=0, net=200, fx=None)

    result = calculate_positions(conn)
    pos = result.positions[('a1', 'i1')]

    assert pos.data_quality_status == 'incomplete'
    assert 'missing_market_price' in pos.quality_warnings
    assert 'missing_fx' in pos.quality_warnings
    assert pos.total_return_chf is None
    assert conn.execute("SELECT COUNT(*) AS n FROM alerts WHERE rule_id IN ('missing_market_price','missing_fx')").fetchone()['n'] >= 2


def test_market_price_enables_unrealized_pnl_total_return_and_snapshot_audit() -> None:
    conn = setup_conn()
    tx(conn, 'b1', 'buy', qty=2, gross=200, fee=0, net=200)
    conn.execute("INSERT INTO market_prices(market_price_id,instrument_id,price_date,close,currency,provider,quality_status,created_at) VALUES('mp1','i1','2026-01-02',120,'CHF','synthetic','ok','now')")

    result = calculate_positions(conn, as_of_date='2026-01-02')
    pos = result.positions[('a1', 'i1')]

    assert pos.market_value_chf == Decimal('240')
    assert pos.unrealized_pnl_chf == Decimal('40')
    assert pos.total_return_chf == Decimal('40')

    save_position_snapshots(conn, result, note='Synthetic calculated snapshot')
    assert conn.execute("SELECT COUNT(*) AS n FROM positions_snapshot").fetchone()['n'] == 1
    assert conn.execute("SELECT COUNT(*) AS n FROM audit_log WHERE action='calculate_position_snapshot'").fetchone()['n'] == 1
