"""Immutable Decision Ledger (IDL) — Merkle-chained receipts in SQLite.

Adapted from PostgreSQL to use SQLite via soagila.db.sqlite_init.
In-memory chain and Merkle hash computation preserved exactly as-is.
"""

import json
import hashlib
import logging
from datetime import datetime, timezone
from typing import Any

from soagila.db.sqlite_init import get_db
from soagila.db.event_bus import publish_event
from soagila.core.idl.receipt import DecisionReceiptModel

logger = logging.getLogger("soagila.idl.ledger")

CHANNEL = "soagila:metrics"

# In-memory chain for fast access (also persisted to SQLite)
_chain: list[DecisionReceiptModel] = []
_chain_initialized = False


def _compute_merkle_hash(receipt_data: dict, prev_hash: str | None) -> str:
    """Compute SHA-256 Merkle hash for a receipt."""
    payload = json.dumps(receipt_data, sort_keys=True, default=str)
    combined = f"{prev_hash or 'GENESIS'}:{payload}"
    return hashlib.sha256(combined.encode()).hexdigest()


async def _ensure_chain_loaded():
    """Load the last receipt from SQLite on first use to maintain chain continuity across restarts."""
    global _chain_initialized
    if _chain_initialized:
        return
    _chain_initialized = True

    if _chain:
        return  # Already has in-memory data

    try:
        db = await get_db()
        cursor = await db.execute(
            """
            SELECT id, session_id, user_id, org_id, receipt_type,
                   memories_accessed, memories_created, arbitration_result,
                   context_hash, response_hash, merkle_hash, prev_merkle_hash,
                   metadata, created_at
            FROM decision_receipts
            ORDER BY created_at DESC
            LIMIT 1
            """
        )
        row = await cursor.fetchone()
        if row:
            # Load only the last receipt — enough to continue the chain
            memories_accessed = json.loads(row[5]) if row[5] else []
            memories_created = json.loads(row[6]) if row[6] else []
            arbitration_result = json.loads(row[7]) if row[7] else None
            metadata = json.loads(row[12]) if row[12] else {}

            last_receipt = DecisionReceiptModel(
                id=str(row[0]),
                session_id=str(row[1]) if row[1] else None,
                user_id=str(row[2]) if row[2] else None,
                org_id=str(row[3]) if row[3] else None,
                receipt_type=row[4],
                memories_accessed=memories_accessed,
                memories_created=memories_created,
                arbitration_result=arbitration_result,
                context_hash=row[8],
                response_hash=row[9],
                merkle_hash=row[10],
                prev_merkle_hash=row[11],
                metadata=metadata,
                created_at=row[13],
            )
            _chain.append(last_receipt)
            logger.info(f"IDL chain resumed from SQLite: last hash={last_receipt.merkle_hash[:16]}...")
    except Exception as e:
        logger.warning(f"Failed to load chain from SQLite: {e}")


async def create_receipt(
    receipt_type: str,
    user_id: str | None = None,
    org_id: str | None = None,
    session_id: str | None = None,
    memories_accessed: list[str] | None = None,
    memories_created: list[str] | None = None,
    arbitration_result: dict | None = None,
    context_hash: str | None = None,
    response_hash: str | None = None,
    metadata: dict | None = None,
    source_agent_id: str | None = None,
    target_agent_id: str | None = None,
    cross_agent_ref: str | None = None,
    # Enterprise enrichment (v0.5.0)
    intent: str | None = None,
    data_lineage: list[str] | None = None,
    pii_touched: list[str] | None = None,
    compliance_tags: list[str] | None = None,
    ide: str | None = None,
    project_hash: str | None = None,
) -> DecisionReceiptModel:
    """
    Create a new Decision Receipt with Merkle chain hash.
    Appends to in-memory chain and persists to SQLite.
    """
    # Ensure chain continuity across server restarts
    await _ensure_chain_loaded()

    # Auto-detect project_hash if not supplied
    if project_hash is None:
        try:
            from soagila.config import get_project_hash
            project_hash = get_project_hash()
        except Exception:
            project_hash = None

    # Auto-detect cloud user if signed in
    cloud_user_id = None
    cloud_email_hash = None
    try:
        from soagila.core import cloud_sync
        u = cloud_sync.current_user()
        if u:
            cloud_user_id = u.get("id")
            import hashlib as _hl
            email = u.get("email", "")
            if email:
                cloud_email_hash = _hl.sha256(email.encode()).hexdigest()
    except Exception:
        pass

    receipt = DecisionReceiptModel(
        receipt_type=receipt_type,
        user_id=user_id,
        org_id=org_id,
        session_id=session_id,
        memories_accessed=memories_accessed or [],
        memories_created=memories_created or [],
        arbitration_result=arbitration_result,
        context_hash=context_hash,
        response_hash=response_hash,
        metadata=metadata or {},
        source_agent_id=source_agent_id,
        target_agent_id=target_agent_id,
        cross_agent_ref=cross_agent_ref,
        intent=intent,
        data_lineage=data_lineage or [],
        pii_touched=pii_touched or [],
        compliance_tags=compliance_tags or [],
        ide=ide,
        project_hash=project_hash,
        cloud_user_id=cloud_user_id,
        cloud_email_hash=cloud_email_hash,
    )

    # Compute risk score from the enriched fields
    receipt.risk_score = receipt.compute_risk_score()

    # Compute Merkle hash — chain now continues from SQLite even after restart
    prev_hash = _chain[-1].merkle_hash if _chain else None
    receipt.prev_merkle_hash = prev_hash

    receipt_data = {
        "id": receipt.id,
        "receipt_type": receipt.receipt_type,
        "user_id": receipt.user_id,
        "memories_accessed": receipt.memories_accessed,
        "memories_created": receipt.memories_created,
        "created_at": receipt.created_at.isoformat(),
    }
    receipt.merkle_hash = _compute_merkle_hash(receipt_data, prev_hash)

    # Append to in-memory chain
    _chain.append(receipt)

    # Persist to SQLite
    await _persist_receipt(receipt)

    # Publish event
    event = json.dumps({
        "type": "receipt_created",
        "receipt_id": receipt.id,
        "receipt_type": receipt.receipt_type,
        "user_id": receipt.user_id,
        "merkle_hash_short": receipt.merkle_hash[:16],
        "chain_length": len(_chain),
        "timestamp": receipt.created_at.isoformat(),
    })
    await publish_event(CHANNEL, event)

    logger.debug(f"Receipt created: {receipt.id} (type={receipt_type}, hash={receipt.merkle_hash[:16]}...)")
    return receipt


async def _persist_receipt(receipt: DecisionReceiptModel):
    """Persist a decision receipt to SQLite."""
    db = await get_db()
    await db.execute(
        """
        INSERT INTO decision_receipts (
            id, session_id, user_id, org_id, receipt_type,
            memories_accessed, memories_created, arbitration_result,
            context_hash, response_hash, merkle_hash, prev_merkle_hash,
            metadata, created_at,
            source_agent_id, target_agent_id, cross_agent_ref
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            receipt.id,
            receipt.session_id,
            receipt.user_id,
            receipt.org_id,
            receipt.receipt_type,
            json.dumps(receipt.memories_accessed),
            json.dumps(receipt.memories_created),
            json.dumps(receipt.arbitration_result) if receipt.arbitration_result else None,
            receipt.context_hash,
            receipt.response_hash,
            receipt.merkle_hash,
            receipt.prev_merkle_hash,
            json.dumps(receipt.metadata),
            receipt.created_at.isoformat() if isinstance(receipt.created_at, datetime) else str(receipt.created_at),
            receipt.source_agent_id,
            receipt.target_agent_id,
            receipt.cross_agent_ref,
        ),
    )
    await db.commit()


async def verify_chain() -> dict:
    """Verify the integrity of the entire Merkle chain from SQLite (not just in-memory)."""
    db = await get_db()
    cursor = await db.execute(
        """
        SELECT id, merkle_hash, prev_merkle_hash
        FROM decision_receipts
        ORDER BY created_at ASC
        """
    )
    rows = await cursor.fetchall()

    if not rows:
        return {"valid": True, "chain_length": 0, "message": "Empty chain"}

    prev_hash = None
    for i, row in enumerate(rows):
        receipt_id = str(row[0])
        merkle_hash = row[1]
        prev_merkle_hash = row[2]

        if prev_merkle_hash != prev_hash:
            return {
                "valid": False,
                "chain_length": len(rows),
                "broken_at": i,
                "receipt_id": receipt_id,
                "message": f"Chain broken at position {i}: prev_hash mismatch",
            }
        prev_hash = merkle_hash

    return {
        "valid": True,
        "chain_length": len(rows),
        "first_hash": rows[0][1][:16] + "..." if rows[0][1] else "",
        "last_hash": rows[-1][1][:16] + "..." if rows[-1][1] else "",
        "message": "Merkle chain integrity verified",
    }


async def get_receipts(
    user_id: str | None = None,
    session_id: str | None = None,
    receipt_type: str | None = None,
    limit: int = 100,
) -> list[DecisionReceiptModel]:
    """Get receipts with optional filtering."""
    db = await get_db()

    conditions = []
    params: list[Any] = []

    if user_id:
        conditions.append("user_id = ?")
        params.append(user_id)
    if session_id:
        conditions.append("session_id = ?")
        params.append(session_id)
    if receipt_type:
        conditions.append("receipt_type = ?")
        params.append(receipt_type)

    where = "WHERE " + " AND ".join(conditions) if conditions else ""
    params.append(limit)

    query = f"""
        SELECT id, session_id, user_id, org_id, receipt_type,
               memories_accessed, memories_created, arbitration_result,
               context_hash, response_hash, merkle_hash, prev_merkle_hash,
               metadata, created_at,
               source_agent_id, target_agent_id, cross_agent_ref
        FROM decision_receipts
        {where}
        ORDER BY created_at DESC
        LIMIT ?
    """
    cursor = await db.execute(query, tuple(params))
    rows = await cursor.fetchall()

    receipts = []
    for row in rows:
        memories_accessed = json.loads(row[5]) if row[5] else []
        memories_created = json.loads(row[6]) if row[6] else []
        arbitration_result = json.loads(row[7]) if row[7] else None
        metadata = json.loads(row[12]) if row[12] else {}

        receipts.append(DecisionReceiptModel(
            id=str(row[0]),
            session_id=str(row[1]) if row[1] else None,
            user_id=str(row[2]) if row[2] else None,
            org_id=str(row[3]) if row[3] else None,
            receipt_type=row[4],
            memories_accessed=memories_accessed,
            memories_created=memories_created,
            arbitration_result=arbitration_result,
            context_hash=row[8],
            response_hash=row[9],
            merkle_hash=row[10],
            prev_merkle_hash=row[11],
            metadata=metadata,
            created_at=row[13],
            source_agent_id=row[14] if len(row) > 14 else None,
            target_agent_id=row[15] if len(row) > 15 else None,
            cross_agent_ref=row[16] if len(row) > 16 else None,
        ))

    return receipts


async def get_receipt(receipt_id: str) -> DecisionReceiptModel | None:
    """Get a single receipt by ID."""
    db = await get_db()
    cursor = await db.execute(
        """
        SELECT id, session_id, user_id, org_id, receipt_type,
               memories_accessed, memories_created, arbitration_result,
               context_hash, response_hash, merkle_hash, prev_merkle_hash,
               metadata, created_at,
               source_agent_id, target_agent_id, cross_agent_ref
        FROM decision_receipts WHERE id = ?
        """,
        (receipt_id,),
    )
    row = await cursor.fetchone()

    if not row:
        return None

    memories_accessed = json.loads(row[5]) if row[5] else []
    memories_created = json.loads(row[6]) if row[6] else []
    arbitration_result = json.loads(row[7]) if row[7] else None
    metadata = json.loads(row[12]) if row[12] else {}

    return DecisionReceiptModel(
        id=str(row[0]),
        session_id=str(row[1]) if row[1] else None,
        user_id=str(row[2]) if row[2] else None,
        org_id=str(row[3]) if row[3] else None,
        receipt_type=row[4],
        memories_accessed=memories_accessed,
        memories_created=memories_created,
        arbitration_result=arbitration_result,
        context_hash=row[8],
        response_hash=row[9],
        merkle_hash=row[10],
        prev_merkle_hash=row[11],
        metadata=metadata,
        created_at=row[13],
        source_agent_id=row[14] if len(row) > 14 else None,
        target_agent_id=row[15] if len(row) > 15 else None,
        cross_agent_ref=row[16] if len(row) > 16 else None,
    )


def get_chain_length() -> int:
    """Get the current in-memory chain length."""
    return len(_chain)


async def repair_chain() -> dict:
    """Repair a broken Merkle chain by re-linking prev_merkle_hash values.

    This fixes chains broken by server restarts (pre-fix versions).
    The merkle_hash of each receipt stays the same — only prev_merkle_hash is corrected.
    """
    db = await get_db()
    cursor = await db.execute(
        "SELECT id, merkle_hash, prev_merkle_hash FROM decision_receipts ORDER BY created_at ASC"
    )
    rows = await cursor.fetchall()

    if not rows:
        return {"repaired": 0, "chain_length": 0}

    repaired = 0
    prev_hash = None

    for row in rows:
        receipt_id = str(row[0])
        merkle_hash = row[1]
        stored_prev = row[2]

        if stored_prev != prev_hash:
            # Fix the link
            await db.execute(
                "UPDATE decision_receipts SET prev_merkle_hash = ? WHERE id = ?",
                (prev_hash, receipt_id),
            )
            repaired += 1

        prev_hash = merkle_hash

    if repaired > 0:
        await db.commit()
        logger.info(f"IDL chain repaired: {repaired} links fixed out of {len(rows)} receipts")

    return {"repaired": repaired, "chain_length": len(rows)}
