"""
Distributed Two-Phase Commit (2PC) Instant Buyout Trade Engine for FreeExile.
Provides atomic trades, immutable ledger, and zero-duplication guarantees (0% dupe).
"""

from __future__ import annotations
import sqlite3
import time
import uuid
import json
import threading
from typing import Dict, Optional, Tuple, Any, List
from dataclasses import dataclass, field
from pathlib import Path

try:
    from server.trade.trade_proxies import (
        StashItemProxy,
        SQLiteAccountStash,
        SQLiteAccountsProxy,
    )
except ImportError:
    from trade.trade_proxies import (
        StashItemProxy,
        SQLiteAccountStash,
        SQLiteAccountsProxy,
    )


@dataclass
class StashItem:
    item_uuid: str
    owner_account_id: str
    item_name: str
    asking_price_currency: str
    asking_price_amount: int
    is_locked: bool = False
    locked_by_tx: Optional[str] = None
    is_scourge_soulbound: bool = False


class InstantBuyoutEngine:
    def __init__(self, market_tax_rate: float = 0.0, db_path: str = "data/trade_ledger.db"):
        self.market_tax_rate = market_tax_rate
        self._lock = threading.RLock()

        if db_path != ":memory:" and not Path(db_path).is_absolute():
            base_dir = Path(__file__).resolve().parent.parent.parent
            self.db_path = str(base_dir / db_path)
            Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
        else:
            self.db_path = db_path

        self.conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
        self.conn.row_factory = sqlite3.Row
        self.conn.execute("PRAGMA journal_mode=WAL;")
        self.conn.execute("PRAGMA synchronous=NORMAL;")
        self.conn.execute("PRAGMA busy_timeout = 30000;")
        self._init_db()
        self.accounts = SQLiteAccountsProxy(self)

    def _init_db(self):
        with self._lock, self.conn:
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS accounts (
                    account_id TEXT PRIMARY KEY,
                    balances TEXT DEFAULT '{}'
                )
            """)
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS items (
                    item_uuid TEXT PRIMARY KEY,
                    owner_account_id TEXT,
                    item_name TEXT,
                    asking_price_currency TEXT,
                    asking_price_amount INTEGER,
                    is_locked INTEGER,
                    locked_by_tx TEXT,
                    is_scourge_soulbound INTEGER
                )
            """)
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS transactions (
                    tx_id TEXT PRIMARY KEY,
                    timestamp_ms INTEGER,
                    item_uuid TEXT,
                    item_name TEXT,
                    seller_account_id TEXT,
                    buyer_account_id TEXT,
                    currency TEXT,
                    amount INTEGER,
                    status TEXT
                )
            """)
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS engine_state (
                    key TEXT PRIMARY KEY,
                    value_int INTEGER
                )
            """)
            self.conn.execute("INSERT OR IGNORE INTO engine_state (key, value_int) VALUES ('total_tax_burned', 0)")

    @property
    def transaction_log(self) -> List[Dict[str, Any]]:
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT * FROM transactions ORDER BY timestamp_ms ASC")
            return [dict(row) for row in cur.fetchall()]

    @property
    def total_tax_burned(self) -> int:
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT value_int FROM engine_state WHERE key = 'total_tax_burned'")
            row = cur.fetchone()
            return row["value_int"] if row else 0

    @total_tax_burned.setter
    def total_tax_burned(self, value: int) -> None:
        with self._lock, self.conn:
            self.conn.execute("UPDATE engine_state SET value_int = ? WHERE key = 'total_tax_burned'", (value,))

    def account_exists(self, account_id: str) -> bool:
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT 1 FROM accounts WHERE account_id = ?", (account_id,))
            return cur.fetchone() is not None

    def ensure_account(self, account_id: str) -> None:
        with self._lock, self.conn:
            self.conn.execute("INSERT OR IGNORE INTO accounts (account_id) VALUES (?)", (account_id,))

    def register_account(self, account_id: str) -> SQLiteAccountStash:
        self.ensure_account(account_id)
        return SQLiteAccountStash(self, account_id)

    def get_balance(self, account_id: str, currency: str) -> int:
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT balances FROM accounts WHERE account_id = ?", (account_id,))
            row = cur.fetchone()
            if not row:
                return 0
            balances = json.loads(row["balances"])
            return balances.get(currency, 0)

    def set_balance(self, account_id: str, currency: str, amount: int) -> None:
        self.ensure_account(account_id)
        with self._lock, self.conn:
            cur = self.conn.cursor()
            cur.execute("SELECT balances FROM accounts WHERE account_id = ?", (account_id,))
            row = cur.fetchone()
            balances = json.loads(row["balances"]) if row else {}
            balances[currency] = amount
            cur.execute("UPDATE accounts SET balances = ? WHERE account_id = ?", (json.dumps(balances), account_id))

    def get_item(self, item_uuid: str) -> Optional[StashItem]:
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT * FROM items WHERE item_uuid = ?", (item_uuid,))
            row = cur.fetchone()
            if not row:
                return None
            return StashItem(
                item_uuid=row["item_uuid"],
                owner_account_id=row["owner_account_id"],
                item_name=row["item_name"],
                asking_price_currency=row["asking_price_currency"],
                asking_price_amount=row["asking_price_amount"],
                is_locked=bool(row["is_locked"]),
                locked_by_tx=row["locked_by_tx"],
                is_scourge_soulbound=bool(row["is_scourge_soulbound"])
            )

    def upsert_item(self, item: StashItem) -> None:
        with self._lock, self.conn:
            self.conn.execute("""
                INSERT OR REPLACE INTO items 
                (item_uuid, owner_account_id, item_name, asking_price_currency, asking_price_amount, is_locked, locked_by_tx, is_scourge_soulbound)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                item.item_uuid, item.owner_account_id, item.item_name, item.asking_price_currency,
                item.asking_price_amount, int(item.is_locked), item.locked_by_tx, int(item.is_scourge_soulbound)
            ))

    def delete_item(self, item_uuid: str) -> None:
        with self._lock, self.conn:
            item = self.get_item(item_uuid)
            if item and item.is_locked:
                raise ValueError(f"Cannot delete item {item_uuid}: locked by transaction {item.locked_by_tx}")
            self.conn.execute("DELETE FROM items WHERE item_uuid = ?", (item_uuid,))

    def prepare_buyout(
        self,
        buyer_account_id: str,
        seller_account_id: str,
        item_uuid: str,
        offered_currency: str,
        offered_amount: int
    ) -> Tuple[bool, str, Optional[str]]:
        """Phase 1 (Prepare): Lock item, escrow buyer funds, and record PREPARED tx."""
        with self._lock:
            item = self.get_item(item_uuid)
            if not item or item.owner_account_id != seller_account_id:
                return False, "ITEM_ALREADY_SOLD: Item not found in seller stash or already sold", None

            if not self.account_exists(seller_account_id) or not self.account_exists(buyer_account_id):
                return False, "TRANSACTION_ABORTED: Invalid buyer or seller account", None

            if buyer_account_id == seller_account_id:
                return False, "TRANSACTION_ABORTED: Cannot purchase own listing", None

            if item.is_scourge_soulbound:
                return False, "TRANSACTION_ABORTED: Item is scourge soulbound and cannot be traded", None

            if item.is_locked:
                return False, "TRANSACTION_ABORTED: Item is currently locked in another pending trade", None

            if item.asking_price_currency != offered_currency or offered_amount < item.asking_price_amount:
                return False, f"TRANSACTION_ABORTED: Price mismatch: item costs {item.asking_price_amount} {item.asking_price_currency}", None

            cur = self.conn.cursor()
            cur.execute("SELECT balances FROM accounts WHERE account_id = ?", (buyer_account_id,))
            row = cur.fetchone()
            buyer_bals = json.loads(row["balances"]) if row and row["balances"] else {}
            buyer_bal = buyer_bals.get(offered_currency, 0)
            if buyer_bal < offered_amount:
                return False, f"TRANSACTION_ABORTED: Insufficient funds: buyer has {buyer_bal} {offered_currency}, needs {offered_amount}", None

            tx_id = f"tx_{uuid.uuid4().hex[:12]}_{int(time.time()*1000)}"
            buyer_bals[offered_currency] -= offered_amount
            with self.conn:
                self.conn.execute("UPDATE accounts SET balances = ? WHERE account_id = ?", (json.dumps(buyer_bals), buyer_account_id))
                self.conn.execute("UPDATE items SET is_locked = 1, locked_by_tx = ? WHERE item_uuid = ?", (tx_id, item_uuid))
                self.conn.execute("""
                    INSERT INTO transactions 
                    (tx_id, timestamp_ms, item_uuid, item_name, seller_account_id, buyer_account_id, currency, amount, status)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'PREPARED')
                """, (tx_id, int(time.time() * 1000), item_uuid, item.item_name, seller_account_id, buyer_account_id, offered_currency, offered_amount))

            return True, "Prepared successfully", tx_id

    def commit_buyout(self, tx_id: str) -> Tuple[bool, str]:
        """Phase 2 (Commit): Credit seller minus burned tax, transfer item, mark COMMITTED."""
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT * FROM transactions WHERE tx_id = ?", (tx_id,))
            tx = cur.fetchone()
            if not tx or tx["status"] != "PREPARED":
                return False, "TRANSACTION_ABORTED: Transaction not found or not in PREPARED state"

            item_uuid = tx["item_uuid"]
            buyer_id = tx["buyer_account_id"]
            seller_id = tx["seller_account_id"]
            currency = tx["currency"]
            amount = tx["amount"]

            tax_burned = int(amount * self.market_tax_rate)
            seller_credit = amount - tax_burned

            cur.execute("SELECT balances FROM accounts WHERE account_id = ?", (seller_id,))
            row = cur.fetchone()
            seller_bals = json.loads(row["balances"]) if row and row["balances"] else {}
            seller_bals[currency] = seller_bals.get(currency, 0) + seller_credit

            with self.conn:
                self.conn.execute("UPDATE accounts SET balances = ? WHERE account_id = ?", (json.dumps(seller_bals), seller_id))
                self.conn.execute("""
                    UPDATE items 
                    SET owner_account_id = ?, is_locked = 0, locked_by_tx = NULL 
                    WHERE item_uuid = ?
                """, (buyer_id, item_uuid))
                self.conn.execute("UPDATE transactions SET status = 'COMMITTED' WHERE tx_id = ?", (tx_id,))
                self.conn.execute("UPDATE engine_state SET value_int = value_int + ? WHERE key = 'total_tax_burned'", (tax_burned,))

            return True, "Instant buyout committed successfully"

    def abort_buyout(self, tx_id: str, reason: str = "Transaction aborted") -> Tuple[bool, str]:
        """Phase 2 (Abort): Unlock item, refund escrowed funds, and record ABORTED status."""
        with self._lock:
            cur = self.conn.cursor()
            cur.execute("SELECT * FROM transactions WHERE tx_id = ?", (tx_id,))
            tx = cur.fetchone()
            if tx and tx["status"] == "PREPARED":
                buyer_id = tx["buyer_account_id"]
                currency = tx["currency"]
                amount = tx["amount"]
                item_uuid = tx["item_uuid"]
                cur.execute("SELECT balances FROM accounts WHERE account_id = ?", (buyer_id,))
                row = cur.fetchone()
                buyer_bals = json.loads(row["balances"]) if row and row["balances"] else {}
                buyer_bals[currency] = buyer_bals.get(currency, 0) + amount
                with self.conn:
                    self.conn.execute("UPDATE accounts SET balances = ? WHERE account_id = ?", (json.dumps(buyer_bals), buyer_id))
                    self.conn.execute("UPDATE items SET is_locked = 0, locked_by_tx = NULL WHERE item_uuid = ?", (item_uuid,))
                    self.conn.execute("UPDATE transactions SET status = 'ABORTED' WHERE tx_id = ?", (tx_id,))
            return False, f"TRANSACTION_ABORTED: {reason}"

    def execute_instant_buyout(
        self,
        buyer_account_id: str,
        seller_account_id: str,
        item_uuid: str,
        offered_currency: str,
        offered_amount: int
    ) -> Tuple[bool, str, Optional[str]]:
        """Unified 2PC coordinator: prepares and commits atomically under lock."""
        with self._lock:
            ok, msg, tx_id = self.prepare_buyout(
                buyer_account_id=buyer_account_id,
                seller_account_id=seller_account_id,
                item_uuid=item_uuid,
                offered_currency=offered_currency,
                offered_amount=offered_amount
            )
            if not ok or not tx_id:
                return False, msg, None

            commit_ok, commit_msg = self.commit_buyout(tx_id)
            if commit_ok:
                return True, "Instant buyout successful", tx_id
            self.abort_buyout(tx_id, commit_msg)
            return False, commit_msg, None
