from __future__ import annotations

from decimal import Decimal

import pytest

from jarvis_finance.crypto.holdings import calculate_crypto_holdings, create_initial_holding_snapshot
from jarvis_finance.crypto.manage import (
    CRYPTO_MANUAL_REVIEW_COIN_LABELS,
    add_crypto_position,
    adjust_crypto_position,
    get_crypto_management_options,
    reduce_crypto_position,
    transfer_crypto_position,
)
from jarvis_finance.crypto.wallets import create_wallet
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)
    return conn


def seed_wallet(conn) -> str:
    return create_wallet(conn, wallet_name="Synthetic Review Wallet", wallet_type="Software Wallet")


def seed_second_wallet(conn) -> str:
    return create_wallet(conn, wallet_name="Synthetic Target Wallet", wallet_type="Exchange")


def audit_count(conn, action: str) -> int:
    return conn.execute("SELECT COUNT(*) AS c FROM audit_log WHERE action=?", (action,)).fetchone()["c"]


def test_add_initial_snapshot_addition_creates_asset_holding_and_audit() -> None:
    conn = setup_conn()
    wallet_id = seed_wallet(conn)

    result = add_crypto_position(
        conn,
        coin_name="Synthetic Cardano",
        symbol="ADA",
        coingecko_id="cardano",
        wallet_id=wallet_id,
        quantity_text="0.00000001",
        effective_date="2025-12-31",
        operation_type="initial_snapshot_addition",
        note="synthetic confirmed addition",
        confirm=True,
    )

    holding = conn.execute("SELECT * FROM crypto_holdings WHERE crypto_holding_id=?", (result.holding_id,)).fetchone()
    asset = conn.execute("SELECT * FROM crypto_assets WHERE asset_id=?", (result.asset_id,)).fetchone()
    assert holding["quantity"] == "0.00000001"
    assert holding["legacy_snapshot_date"] == "2025-12-31"
    assert asset["coingecko_id"] == "cardano"
    assert audit_count(conn, "initial_crypto_holding_snapshot") == 1
    assert result.warnings == []


def test_add_coin_without_coingecko_id_creates_warning_and_preserves_decimal_text() -> None:
    conn = setup_conn()
    wallet_id = seed_wallet(conn)

    result = add_crypto_position(
        conn,
        coin_name="Synthetic Mystery",
        symbol="MYST",
        coingecko_id=None,
        wallet_id=wallet_id,
        quantity_text="0.00000001",
        effective_date="2025-12-31",
        operation_type="initial_snapshot_addition",
        note="synthetic uncertain mapping accepted",
        coingecko_status="missing_confirmed",
        confirm=True,
    )

    stored = conn.execute("SELECT quantity FROM crypto_holdings WHERE crypto_holding_id=?", (result.holding_id,)).fetchone()["quantity"]
    assert stored == "0.00000001"
    assert conn.execute("SELECT COUNT(*) AS c FROM alerts WHERE rule_id='missing_coingecko_id'").fetchone()["c"] >= 1
    assert "missing_coingecko_id" in result.warnings


def test_manual_adjustment_requires_note_and_records_old_new_values() -> None:
    conn = setup_conn()
    wallet_id = seed_wallet(conn)
    added = add_crypto_position(
        conn,
        coin_name="Synthetic Bitcoin",
        symbol="BTC",
        coingecko_id="bitcoin",
        wallet_id=wallet_id,
        quantity_text="1.25",
        effective_date="2025-12-31",
        operation_type="initial_snapshot_addition",
        note="synthetic initial",
        confirm=True,
    )

    with pytest.raises(ValueError, match="note"):
        adjust_crypto_position(
            conn,
            asset_id=added.asset_id,
            wallet_id=wallet_id,
            new_quantity_text="1.30",
            effective_date="2026-01-15",
            note="",
            confirm=True,
        )

    txid = adjust_crypto_position(
        conn,
        asset_id=added.asset_id,
        wallet_id=wallet_id,
        new_quantity_text="1.30",
        effective_date="2026-01-15",
        note="synthetic correction note",
        confirm=True,
    ).transaction_id

    holdings = calculate_crypto_holdings(conn)
    assert holdings.wallet_holdings[(wallet_id, added.asset_id)].quantity == Decimal("1.30")
    audit = conn.execute("SELECT old_values_json, new_values_json FROM audit_log WHERE entity_id=? AND action='crypto_manual_adjustment'", (txid,)).fetchone()
    assert '"quantity": "1.25"' in audit["old_values_json"]
    assert '"quantity": "1.30"' in audit["new_values_json"]


def test_reduce_to_zero_preserves_holding_row_without_delete_and_blocks_negative() -> None:
    conn = setup_conn()
    wallet_id = seed_wallet(conn)
    added = add_crypto_position(
        conn,
        coin_name="Synthetic Ether",
        symbol="ETH",
        coingecko_id="ethereum",
        wallet_id=wallet_id,
        quantity_text="2",
        effective_date="2025-12-31",
        operation_type="initial_snapshot_addition",
        note="synthetic initial",
        confirm=True,
    )

    reduce_crypto_position(
        conn,
        asset_id=added.asset_id,
        wallet_id=wallet_id,
        reduction_quantity_text="2",
        effective_date="2026-02-01",
        note="synthetic remove via adjustment",
        confirm=True,
    )

    assert conn.execute("SELECT COUNT(*) AS c FROM crypto_holdings WHERE asset_id=? AND wallet_id=?", (added.asset_id, wallet_id)).fetchone()["c"] == 1
    assert calculate_crypto_holdings(conn).wallet_holdings[(wallet_id, added.asset_id)].quantity == Decimal("0")

    with pytest.raises(ValueError, match="negative"):
        reduce_crypto_position(
            conn,
            asset_id=added.asset_id,
            wallet_id=wallet_id,
            reduction_quantity_text="0.00000001",
            effective_date="2026-02-01",
            note="synthetic over reduce",
            confirm=True,
        )


def test_transfer_between_wallets_via_manage_helper_is_audited() -> None:
    conn = setup_conn()
    source = seed_wallet(conn)
    target = seed_second_wallet(conn)
    added = add_crypto_position(conn, coin_name="Synthetic Ether", symbol="ETH", coingecko_id="ethereum", wallet_id=source, quantity_text="4", effective_date="2025-12-31", operation_type="initial_snapshot_addition", note="synthetic initial", confirm=True)

    result = transfer_crypto_position(conn, asset_id=added.asset_id, from_wallet_id=source, to_wallet_id=target, quantity_text="1.5", fee_quantity_text="0.1", effective_date="2026-03-01", note="synthetic transfer", confirm=True)

    holdings = calculate_crypto_holdings(conn)
    assert holdings.wallet_holdings[(source, added.asset_id)].quantity == Decimal("2.4")
    assert holdings.wallet_holdings[(target, added.asset_id)].quantity == Decimal("1.5")
    assert result.transaction_id
    assert audit_count(conn, "crypto_transfer") == 1


def test_validations_reject_bad_amounts_wallets_and_unconfirmed_writes() -> None:
    conn = setup_conn()
    wallet_id = seed_wallet(conn)

    with pytest.raises(ValueError, match="quantity"):
        add_crypto_position(conn, coin_name="Synthetic Bad", symbol="BAD", wallet_id=wallet_id, quantity_text="0", effective_date="2025-12-31", operation_type="initial_snapshot_addition", note="synthetic", confirm=True)
    with pytest.raises(ValueError, match="wallet"):
        add_crypto_position(conn, coin_name="Synthetic Bad", symbol="BAD", wallet_id="", quantity_text="1", effective_date="2025-12-31", operation_type="initial_snapshot_addition", note="synthetic", confirm=True)
    with pytest.raises(ValueError, match="symbol"):
        add_crypto_position(conn, coin_name="Synthetic Bad", symbol="", wallet_id=wallet_id, quantity_text="1", effective_date="2025-12-31", operation_type="initial_snapshot_addition", note="synthetic", confirm=True)
    with pytest.raises(ValueError, match="confirm"):
        add_crypto_position(conn, coin_name="Synthetic Bad", symbol="BAD", wallet_id=wallet_id, quantity_text="1", effective_date="2025-12-31", operation_type="initial_snapshot_addition", note="synthetic", confirm=False)


def test_crypto_management_options_include_manual_review_list() -> None:
    conn = setup_conn()
    options = get_crypto_management_options(conn)
    assert "ADA Cardano" in CRYPTO_MANUAL_REVIEW_COIN_LABELS
    assert "USTC Terra Classic" in options["manual_review_coin_labels"]
    assert options["wallets"] == []


def test_manual_adjustment_transaction_type_is_used_by_holding_calculation() -> None:
    conn = setup_conn()
    wallet_id = seed_wallet(conn)
    asset = add_crypto_position(
        conn,
        coin_name="Synthetic Solana",
        symbol="SOL",
        coingecko_id="solana",
        wallet_id=wallet_id,
        quantity_text="1",
        effective_date="2025-12-31",
        operation_type="initial_snapshot_addition",
        note="synthetic initial",
        confirm=True,
    ).asset_id

    adjust_crypto_position(conn, asset_id=asset, wallet_id=wallet_id, new_quantity_text="1.00000001", effective_date="2026-01-01", note="synthetic tiny correction", confirm=True)

    assert calculate_crypto_holdings(conn).wallet_holdings[(wallet_id, asset)].quantity == Decimal("1.00000001")
    assert conn.execute("SELECT COUNT(*) AS c FROM audit_log WHERE action='crypto_manual_adjustment'").fetchone()["c"] == 1
