"""
Sophi Submarine Swap Proof Builder
Generates all cryptographic parameters needed for a submarine swap.

A submarine swap is an atomic exchange between Bitcoin on-chain and Lightning.
The same HTLC secret_hash locks both sides simultaneously.

Output (SubmarineProof) contains everything an executor needs:
    - htlc_script:    the Bitcoin locking script (hex)
    - payment_hash:   the Lightning payment hash (= secret_hash)
    - timeouts:       lock and claim timeouts
    - amounts:        on-chain and lightning amounts
    - secret_hash:    SHA-256 commitment
    - secret:         the preimage (keep safe, never log)
"""

import hashlib
import secrets
import struct
import time
from dataclasses import dataclass, field
from typing import Optional, Dict, Any

from .compiler import CompiledSwap
from .parser import PlainValue, BlindValue


# ── Data structures ────────────────────────────────────────────────────────────

@dataclass
class HTLCScript:
    """
    Raw HTLC locking script parameters.
    This is what goes on-chain to lock the Bitcoin side.

    Script logic (in pseudocode):
        IF sha256(preimage) == payment_hash AND sig(receiver) THEN claim
        ELSE IF blockheight >= timeout AND sig(sender) THEN refund
    """
    payment_hash:    str    # hex, 64 chars — sha256 of the secret
    sender_pubkey:   str    # hex — who can refund (Alice)
    receiver_pubkey: str    # hex — who can claim (Bob)
    timeout_blocks:  int    # absolute block height for refund

    def to_script_hex(self) -> str:
        """
        Builds the HTLC script in Bitcoin Script opcodes.
        This is the raw script that goes into a P2WSH output.

        OP_IF
            OP_SHA256 <payment_hash> OP_EQUALVERIFY
            OP_DUP OP_HASH160 <receiver_pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
        OP_ELSE
            <timeout> OP_CHECKLOCKTIMEVERIFY OP_DROP
            OP_DUP OP_HASH160 <sender_pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
        OP_ENDIF
        """
        # Opcodes
        OP_IF              = "63"
        OP_ELSE            = "67"
        OP_ENDIF           = "68"
        OP_SHA256          = "a8"
        OP_EQUALVERIFY     = "88"
        OP_DUP             = "76"
        OP_HASH160         = "a9"
        OP_CHECKSIG        = "ac"
        OP_CHECKLOCKTIMEVERIFY = "b1"
        OP_DROP            = "75"
        PUSH_20            = "14"   # push 20 bytes
        PUSH_32            = "20"   # push 32 bytes

        def pubkey_hash(pubkey_hex: str) -> str:
            """HASH160 = RIPEMD160(SHA256(pubkey))"""
            pub_bytes = bytes.fromhex(pubkey_hex) if pubkey_hex else bytes(20)
            sha = hashlib.sha256(pub_bytes).digest()
            ripemd = hashlib.new('ripemd160', sha).digest()
            return ripemd.hex()

        def encode_locktime(n: int) -> str:
            """Encode locktime as minimal push"""
            if n == 0:
                return "00"
            elif n <= 16:
                return hex(0x50 + n)[2:]
            else:
                # little-endian, minimal encoding
                b = n.to_bytes((n.bit_length() + 7) // 8, 'little')
                return f"{len(b):02x}" + b.hex()

        ph = self.payment_hash
        rh = pubkey_hash(self.receiver_pubkey)
        sh = pubkey_hash(self.sender_pubkey)
        lt = encode_locktime(self.timeout_blocks)

        script = (
            OP_IF +
            OP_SHA256 + PUSH_32 + ph + OP_EQUALVERIFY +
            OP_DUP + OP_HASH160 + PUSH_20 + rh + OP_EQUALVERIFY + OP_CHECKSIG +
            OP_ELSE +
            lt + OP_CHECKLOCKTIMEVERIFY + OP_DROP +
            OP_DUP + OP_HASH160 + PUSH_20 + sh + OP_EQUALVERIFY + OP_CHECKSIG +
            OP_ENDIF
        )
        return script

    def to_p2wsh_hex(self) -> str:
        """
        Wraps the HTLC script in a P2WSH output script.
        This is what gets embedded in the Bitcoin transaction output.

        P2WSH = OP_0 <sha256(htlc_script)>
        """
        OP_0 = "00"
        PUSH_32 = "20"
        script_bytes = bytes.fromhex(self.to_script_hex())
        script_hash = hashlib.sha256(script_bytes).hexdigest()
        return OP_0 + PUSH_32 + script_hash


@dataclass
class SubmarineProof:
    """
    Everything an executor needs to perform a submarine swap.
    Pass this to any executor — Bitcoin node, Boltz, your own code.
    """
    # Cryptographic core
    secret:          str    # preimage — NEVER log, keep in memory only
    secret_hash:     str    # sha256(secret) = payment_hash
    payment_hash:    str    # same as secret_hash, used in Lightning invoice

    # On-chain side
    htlc_script:     HTLCScript
    onchain_amount_sat: int

    # Lightning side
    lightning_amount_msat: int   # millisatoshis

    # Timing
    timeout_blocks:  int
    expiration_unix: int

    # Metadata
    sender:          str    # party name from .sph
    receiver:        str    # party name from .sph
    swap_direction:  str    # "onchain_to_lightning" | "lightning_to_onchain"

    def to_dict(self) -> Dict[str, Any]:
        return {
            "secret_hash":          self.secret_hash,
            "payment_hash":         self.payment_hash,
            "htlc_script_hex":      self.htlc_script.to_script_hex(),
            "p2wsh_hex":            self.htlc_script.to_p2wsh_hex(),
            "onchain_amount_sat":   self.onchain_amount_sat,
            "lightning_amount_msat": self.lightning_amount_msat,
            "timeout_blocks":       self.timeout_blocks,
            "expiration_unix":      self.expiration_unix,
            "sender":               self.sender,
            "receiver":             self.receiver,
            "swap_direction":       self.swap_direction,
        }

    def summary(self) -> str:
        lines = [
            "── Sophi submarine swap proof ──────────────────",
            f"  direction     : {self.swap_direction}",
            f"  onchain       : {self.onchain_amount_sat} sat",
            f"  lightning     : {self.lightning_amount_msat} msat",
            f"  payment_hash  : {self.payment_hash[:16]}...",
            f"  htlc_script   : {self.htlc_script.to_script_hex()[:32]}...",
            f"  p2wsh         : {self.htlc_script.to_p2wsh_hex()[:32]}...",
            f"  timeout       : block {self.timeout_blocks}",
            f"  sender        : {self.sender}",
            f"  receiver      : {self.receiver}",
        ]
        return "\n".join(lines)


# ── Proof builder ──────────────────────────────────────────────────────────────

# BTC to satoshi conversion
def _to_sat(amount: float, asset: str) -> int:
    if asset.upper() in ("BTC", "BLIND"):
        return int(amount * 100_000_000)
    return int(amount)  # already in sat

# Default placeholder pubkeys (executor replaces with real ones)
_PLACEHOLDER_SENDER   = "02" + "aa" * 32
_PLACEHOLDER_RECEIVER = "02" + "bb" * 32
_DEFAULT_TIMEOUT      = 144   # ~24 hours in blocks


def build_submarine_proof(
    compiled: CompiledSwap,
    sender_pubkey:   str = None,
    receiver_pubkey: str = None,
    timeout_blocks:  int = None,
) -> SubmarineProof:
    """
    Builds a SubmarineProof from a compiled Sophi swap.

    sender_pubkey / receiver_pubkey:
        Compressed public keys (hex, 33 bytes = 66 hex chars).
        If not provided, placeholder values are used — replace before broadcasting.

    timeout_blocks:
        Absolute block height for the refund path.
        Defaults to current_block_estimate + 144 (~24h).
    """
    secret      = compiled.htlc_params["secret"]
    secret_hash = compiled.htlc_params["secret_hash"]

    parties = compiled.intents
    if len(parties) < 2:
        raise ValueError("Submarine swap requires exactly 2 parties.")

    sender_intent   = parties[0]
    receiver_intent = parties[1]

    sender_name   = sender_intent["metadata"]["sophi_party"]
    receiver_name = receiver_intent["metadata"]["sophi_party"]

    # Determine amounts
    onchain_amount_sat = _to_sat(
        sender_intent["amount"],
        sender_intent["sell_asset"]
    )
    # Lightning amount = same value in millisatoshis
    lightning_amount_msat = onchain_amount_sat * 1000

    # Timeout
    if timeout_blocks is None:
        expiration = compiled.htlc_params["expiration"]
        APPROX_CURRENT_BLOCK = 840000
        APPROX_BLOCK_TIME_SEC = 600
        now = int(time.time())
        blocks_remaining = max(144, (expiration - now) // APPROX_BLOCK_TIME_SEC)
        timeout_blocks = APPROX_CURRENT_BLOCK + blocks_remaining

    htlc = HTLCScript(
        payment_hash    = secret_hash,
        sender_pubkey   = sender_pubkey   or _PLACEHOLDER_SENDER,
        receiver_pubkey = receiver_pubkey or _PLACEHOLDER_RECEIVER,
        timeout_blocks  = timeout_blocks,
    )

    return SubmarineProof(
        secret               = secret,
        secret_hash          = secret_hash,
        payment_hash         = secret_hash,
        htlc_script          = htlc,
        onchain_amount_sat   = onchain_amount_sat,
        lightning_amount_msat = lightning_amount_msat,
        timeout_blocks       = timeout_blocks,
        expiration_unix      = compiled.htlc_params["expiration"],
        sender               = sender_name,
        receiver             = receiver_name,
        swap_direction       = "onchain_to_lightning",
    )
