from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from decimal import Decimal, ROUND_HALF_UP
from sqlite3 import Connection

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.imports.common import stable_id, utc_now
from jarvis_finance.quality.alerts import create_alert
from jarvis_finance.fx.rates import latest_fx_rate

from .cost_basis import WeightedAverageLot

MONEY = Decimal("0.01")


def _d(value: object, default: str = "0") -> Decimal:
    if value is None or str(value).strip() == "":
        return Decimal(default)
    return Decimal(str(value))


def _money(value: Decimal | None) -> Decimal | None:
    if value is None:
        return None
    return value.quantize(MONEY, rounding=ROUND_HALF_UP)


@dataclass
class Position:
    account_id: str
    instrument_id: str
    quantity: Decimal = Decimal("0")
    cost_basis_original: Decimal = Decimal("0")
    cost_basis_chf: Decimal = Decimal("0")
    average_cost_original: Decimal | None = None
    average_cost_chf: Decimal | None = None
    realized_pnl_chf: Decimal = Decimal("0")
    income_chf: Decimal = Decimal("0")
    fees_chf: Decimal = Decimal("0")
    taxes_chf: Decimal = Decimal("0")
    market_price_original: Decimal | None = None
    market_value_original: Decimal | None = None
    market_value_chf: Decimal | None = None
    unrealized_pnl_chf: Decimal | None = None
    total_return_chf: Decimal | None = None
    data_quality_status: str = "ok"
    valuation_status: str = "not_valuable"
    valuation_timestamp_status: str = "missing_timestamp"
    hedge_status: str = "unknown"
    instrument_status: str = "unknown"
    valuation_policy: str = "live_price"
    corporate_action_status: str = "not_checked"
    quality_warnings: list[str] = field(default_factory=list)


@dataclass
class PositionCalculationResult:
    positions: dict[tuple[str, str], Position] = field(default_factory=dict)
    as_of_date: str | None = None


def _latest_market_price(conn: Connection, instrument_id: str, as_of_date: str | None):
    if as_of_date:
        return conn.execute(
            """
            SELECT close, currency, price_timestamp, price_date, corporate_action_status FROM market_prices
            WHERE instrument_id=? AND price_date <= ? AND quality_status IN ('fresh','ok')
            ORDER BY price_date DESC, created_at DESC LIMIT 1
            """,
            (instrument_id, as_of_date),
        ).fetchone()
    return conn.execute(
        """
        SELECT close, currency, price_timestamp, price_date, corporate_action_status FROM market_prices
        WHERE instrument_id=? AND quality_status IN ('fresh','ok')
        ORDER BY price_date DESC, created_at DESC LIMIT 1
        """,
        (instrument_id,),
    ).fetchone()


def _add_warning(conn: Connection, pos: Position, rule_id: str, message: str) -> None:
    if rule_id not in pos.quality_warnings:
        pos.quality_warnings.append(rule_id)
    pos.data_quality_status = "incomplete"
    create_alert(
        conn,
        priority="kritisch" if rule_id == "missing_fx" else "warnung",
        category="ledger",
        entity_type="instrument",
        entity_id=pos.instrument_id,
        rule_id=rule_id,
        message=message,
        evidence={"account_id": pos.account_id, "instrument_id": pos.instrument_id},
    )


def _timestamp_status(price_ts: str | None, fx_ts: str | None, *, max_delta_hours: int = 24) -> str:
    if not price_ts or not fx_ts:
        return "missing_timestamp"
    try:
        p = datetime.fromisoformat(price_ts.replace("Z", "+00:00"))
        f = datetime.fromisoformat(fx_ts.replace("Z", "+00:00"))
        if p.tzinfo is None:
            p = p.replace(tzinfo=timezone.utc)
        if f.tzinfo is None:
            f = f.replace(tzinfo=timezone.utc)
    except ValueError:
        return "missing_timestamp"
    delta_hours = abs((p - f).total_seconds()) / 3600
    if delta_hours <= 1:
        return "aligned"
    if delta_hours <= max_delta_hours:
        return "acceptable"
    return "stale_mismatch"


def _instrument_metadata(conn: Connection, instrument_id: str) -> dict[str, object]:
    row = conn.execute("SELECT * FROM instruments WHERE instrument_id=?", (instrument_id,)).fetchone()
    if not row:
        return {"hedge_status": "unknown", "instrument_status": "unknown", "valuation_policy": "live_price", "corporate_action_status": "not_checked"}
    return dict(row)


def calculate_positions(conn: Connection, *, as_of_date: str | None = None, max_price_fx_time_delta_hours: int = 24) -> PositionCalculationResult:
    result = PositionCalculationResult(as_of_date=as_of_date)
    lots: dict[tuple[str, str], WeightedAverageLot] = {}
    params: tuple[object, ...] = ()
    where = "WHERE instrument_id IS NOT NULL AND COALESCE(is_voided, 0) = 0"
    if as_of_date:
        where += " AND trade_date <= ?"
        params = (as_of_date,)
    rows = conn.execute(
        f"""
        SELECT * FROM transactions
        {where}
        ORDER BY account_id, instrument_id, trade_date, created_at, transaction_id
        """,
        params,
    ).fetchall()
    for row in rows:
        key = (row["account_id"], row["instrument_id"])
        pos = result.positions.setdefault(key, Position(row["account_id"], row["instrument_id"]))
        lot = lots.setdefault(key, WeightedAverageLot())
        t = row["transaction_type"]
        qty = _d(row["quantity"])
        gross = _d(row["gross_amount_original"])
        fee = _d(row["fee_original"])
        tax = _d(row["tax_original"])
        fx = _d(row["fx_rate_to_chf"], "1") if row["fx_rate_to_chf"] is not None else None
        if fx is None or row["fx_status"] == "missing":
            _add_warning(conn, pos, "missing_fx", "Historic FX is missing; precise CHF total return cannot be calculated.")
        if t in {"initial_position_snapshot", "buy"}:
            if qty > 0:
                cost_original = gross + fee
                cost_chf = (gross + fee) * fx if fx is not None else Decimal("0")
                lot.buy(qty, cost_original, cost_chf)
                pos.fees_chf += fee * fx if fx is not None else Decimal("0")
                pos.taxes_chf += tax * fx if fx is not None else Decimal("0")
        elif t in {"partial_sell", "full_sell"}:
            if qty > 0:
                proceeds_chf = (gross - fee - tax) * fx if fx is not None else Decimal("0")
                lot.sell(qty, proceeds_chf)
                pos.fees_chf += fee * fx if fx is not None else Decimal("0")
                pos.taxes_chf += tax * fx if fx is not None else Decimal("0")
        elif t in {"dividend", "etf_distribution"}:
            income_original = _d(row["net_amount_original"]) if row["net_amount_original"] is not None else gross - fee - tax
            if fx is not None:
                pos.income_chf += income_original * fx
                pos.fees_chf += fee * fx
                pos.taxes_chf += tax * fx
        if t == "initial_position_snapshot":
            _add_warning(conn, pos, "snapshot_only", "Initial snapshot lacks full transaction history; performance is incomplete.")
            if gross == 0 or fx is None:
                _add_warning(conn, pos, "cost_basis_uncertain", "Cost basis is uncertain for this initial snapshot.")
    for key, lot in lots.items():
        pos = result.positions[key]
        meta = _instrument_metadata(conn, pos.instrument_id)
        pos.hedge_status = str(meta.get("hedge_status") or "unknown")
        pos.instrument_status = str(meta.get("instrument_status") or "unknown")
        pos.valuation_policy = str(meta.get("valuation_policy") or "live_price")
        pos.corporate_action_status = str(meta.get("corporate_action_status") or "not_checked")
        if pos.hedge_status == "unknown":
            _add_warning(conn, pos, "hedge_status_unknown", "Currency hedge status is unknown; FX attribution is incomplete.")
        if pos.instrument_status in {"delisted", "suspended", "merged", "inactive"} and pos.valuation_policy in {"live_price", "exclude_from_auto_price_update"}:
            _add_warning(conn, pos, "delisted_or_suspended", "Instrument status excludes or limits automatic valuation.")
        if pos.corporate_action_status in {"suspected", "confirmed"} or meta.get("split_or_corporate_action_review_required"):
            _add_warning(conn, pos, "corporate_action_suspected", "Corporate action review is required before high-confidence valuation.")
        pos.quantity = lot.quantity.normalize()
        pos.cost_basis_original = _money(lot.cost_basis_original) or Decimal("0")
        pos.cost_basis_chf = _money(lot.cost_basis_chf) or Decimal("0")
        pos.average_cost_original = lot.average_cost_original
        pos.average_cost_chf = lot.average_cost_chf
        pos.realized_pnl_chf = _money(lot.realized_pnl_chf) or Decimal("0")
        pos.income_chf = _money(pos.income_chf) or Decimal("0")
        pos.fees_chf = _money(pos.fees_chf) or Decimal("0")
        pos.taxes_chf = _money(pos.taxes_chf) or Decimal("0")
        market = _latest_market_price(conn, pos.instrument_id, as_of_date)
        if market is None:
            _add_warning(conn, pos, "missing_market_price", "Market price is missing; unrealized P&L cannot be calculated.")
        else:
            pos.market_price_original = _d(market["close"])
            pos.market_value_original = _money(pos.quantity * pos.market_price_original)
            # MVP: use only local FX data; dashboards never call providers during render.
            if market["currency"] == "CHF":
                pos.market_value_chf = pos.market_value_original
                pos.valuation_timestamp_status = "aligned" if market["price_timestamp"] or market["price_date"] else "missing_timestamp"
                pos.unrealized_pnl_chf = _money(pos.market_value_chf - pos.cost_basis_chf)
            else:
                fx_row = latest_fx_rate(conn, base_currency=market["currency"], quote_currency="CHF", rate_date=as_of_date)
                if fx_row:
                    market_fx = _d(fx_row["rate"])
                    pos.valuation_timestamp_status = _timestamp_status(market["price_timestamp"] or market["price_date"], fx_row["rate_timestamp"] or fx_row["rate_date"], max_delta_hours=max_price_fx_time_delta_hours)
                    if pos.valuation_timestamp_status == "stale_mismatch":
                        _add_warning(conn, pos, "stale_valuation", "Price and FX timestamps are outside the configured valuation tolerance.")
                    pos.market_value_chf = _money(pos.market_value_original * market_fx) if pos.market_value_original is not None else None
                    if pos.market_value_chf is not None:
                        pos.unrealized_pnl_chf = _money(pos.market_value_chf - pos.cost_basis_chf)
                    if pos.hedge_status == "hedged" and (meta.get("hedged_to_currency") or "").upper() == "CHF":
                        _add_warning(conn, pos, "currency_hedged_fx_attribution", "CHF-hedged instrument: free FX P&L attribution is methodically restricted.")
                else:
                    _add_warning(conn, pos, "missing_fx", "Market FX is missing; CHF valuation is incomplete.")
            if market["corporate_action_status"] in {"suspected", "confirmed"}:
                _add_warning(conn, pos, "corporate_action_suspected", "Market price may cross a corporate action; manual review required.")
        if pos.market_value_chf is not None and "missing_fx" not in pos.quality_warnings and "cost_basis_uncertain" not in pos.quality_warnings and "snapshot_only" not in pos.quality_warnings:
            pos.total_return_chf = _money((pos.unrealized_pnl_chf or Decimal("0")) + pos.realized_pnl_chf + pos.income_chf - pos.fees_chf - pos.taxes_chf)
        else:
            pos.total_return_chf = None
        blocking = {"missing_market_price", "missing_fx", "hedge_status_unknown", "stale_valuation", "corporate_action_suspected", "delisted_or_suspended"}
        if pos.quantity == 0 or pos.instrument_status in {"delisted", "suspended", "merged", "inactive"} and pos.valuation_policy == "exclude_from_auto_price_update":
            pos.valuation_status = "not_valuable"
        elif pos.market_value_chf is not None and not (blocking & set(pos.quality_warnings)):
            pos.valuation_status = "valuable"
        elif pos.quantity != 0:
            pos.valuation_status = "partially_valuable"
        else:
            pos.valuation_status = "not_valuable"
    conn.commit()
    return result


def save_position_snapshots(conn: Connection, result: PositionCalculationResult, *, note: str) -> list[str]:
    if not note.strip():
        raise ValueError("position snapshot requires a note")
    now = utc_now()
    snapshot_date = result.as_of_date or now[:10]
    ids: list[str] = []
    for (account_id, instrument_id), pos in result.positions.items():
        row = conn.execute("SELECT platform_id FROM accounts WHERE account_id=?", (account_id,)).fetchone()
        platform_id = row["platform_id"] if row else None
        snapshot_id = stable_id("possnap", snapshot_date, account_id, instrument_id)
        conn.execute(
            """
            INSERT OR REPLACE INTO positions_snapshot(
                position_snapshot_id, snapshot_date, account_id, platform_id, instrument_id,
                quantity, average_cost_original, cost_basis_original, cost_basis_chf,
                market_price_original, market_value_original, market_value_chf,
                unrealized_price_pnl_chf, realized_pnl_chf, income_chf, fees_chf,
                taxes_chf, total_return_chf, data_quality_status, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                snapshot_id, snapshot_date, account_id, platform_id, instrument_id,
                str(pos.quantity), str(pos.average_cost_original) if pos.average_cost_original is not None else None,
                str(pos.cost_basis_original), str(pos.cost_basis_chf),
                str(pos.market_price_original) if pos.market_price_original is not None else None,
                str(pos.market_value_original) if pos.market_value_original is not None else None,
                str(pos.market_value_chf) if pos.market_value_chf is not None else None,
                str(pos.unrealized_pnl_chf) if pos.unrealized_pnl_chf is not None else None,
                str(pos.realized_pnl_chf), str(pos.income_chf), str(pos.fees_chf),
                str(pos.taxes_chf), str(pos.total_return_chf) if pos.total_return_chf is not None else None,
                pos.data_quality_status, now,
            ),
        )
        record_audit_event(
            conn,
            source="ledger_positions",
            action="calculate_position_snapshot",
            entity_type="position_snapshot",
            entity_id=snapshot_id,
            new_values={"account_id": account_id, "instrument_id": instrument_id, "snapshot_date": snapshot_date},
            user_text_note=note,
            confirmed=True,
            created_by="system",
        )
        ids.append(snapshot_id)
    conn.commit()
    return ids
