"""
AICF Development Node Stub
===========================

**WARNING: This is a test-only stub RPC server for development and testing.**

This module provides a minimal RPC server that simulates basic blockchain
operations with synthetic blocks. It is NOT production code and should NEVER
be used in production environments.

Purpose:
- Enable local development without running a full node
- Support integration tests for AICF components
- Provide a simple target for CLI testing

Limitations:
- Blocks use trivial difficulty (0x1) and are not mined
- No real consensus, state transitions, or transaction execution
- No persistence beyond a simple JSON state file
- Hashes are computed from block numbers, not actual content

For production use, connect to a real Animica node running the full
consensus, execution, and persistence layers.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import os
import signal
import sys
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path

state = {"height": 0, "chain_id": "0xa11ca", "auto_mine": False}
lock = threading.Lock()
state_file: Path | None = None


def load_state(datadir: str) -> None:
    global state_file
    state_file = Path(datadir) / "state.json"
    state_file.parent.mkdir(parents=True, exist_ok=True)
    if state_file.exists():
        try:
            saved = json.loads(state_file.read_text())
            with lock:
                state["height"] = int(saved.get("height", 0))
                state["chain_id"] = saved.get("chain_id", state["chain_id"])
        except Exception:
            pass


def save_state() -> None:
    if state_file is None:
        return
    payload = json.dumps({"height": state["height"], "chain_id": state["chain_id"]})
    if lock.locked():
        state_file.write_text(payload)
    else:
        with lock:
            state_file.write_text(payload)


def make_block(n: int) -> dict:
    """
    Generate a synthetic block for testing.
    
    **TEST-ONLY**: This function creates fake blocks with synthetic hashes
    and trivial difficulty. It is used only for development and testing.
    
    Real block construction should use:
    - consensus/difficulty.py for real difficulty calculation
    - mining/hash_search.py for proper nonce finding
    - core/types/block.py for canonical block structure
    - core/db/block_db.py for persistence
    
    Args:
        n: Block height (number)
        
    Returns:
        dict: Synthetic block with fake hashes and trivial difficulty
    """
    parent = (
        "0x" + "0" * 64
        if n <= 0
        else "0x" + hashlib.sha256(f"{n-1}".encode()).hexdigest()
    )
    hsh = "0x" + hashlib.sha256(f"{n}".encode()).hexdigest()
    return {
        "number": hex(n),
        "hash": hsh,
        "parentHash": parent,
        "timestamp": hex(int(time.time())),
        "difficulty": "0x1",  # TEST-ONLY: trivial difficulty, not real consensus
        "nonce": "0x0",  # TEST-ONLY: no real mining/nonce search
        "miner": "0x" + "0" * 40,  # TEST-ONLY: placeholder address
        "transactions": [],
    }


class Handler(BaseHTTPRequestHandler):
    def _send(self, obj: dict, code: int = 200):
        data = json.dumps(obj).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.send_header("Access-Control-Allow-Origin", "*")
        self.end_headers()
        self.wfile.write(data)

    def do_POST(self):
        try:
            self.connection.settimeout(2)
            length = int(self.headers.get("content-length", "0"))
            body = self.rfile.read(length)
        except Exception:
            self._send(
                {
                    "jsonrpc": "2.0",
                    "id": None,
                    "error": {"code": -32700, "message": "Parse error"},
                },
                400,
            )
            return
        try:
            req = json.loads(body or b"{}")
        except Exception:
            self._send(
                {
                    "jsonrpc": "2.0",
                    "id": None,
                    "error": {"code": -32700, "message": "Parse error"},
                },
                400,
            )
            return

        if not isinstance(req, dict):
            self._send(
                {
                    "jsonrpc": "2.0",
                    "id": None,
                    "error": {"code": -32600, "message": "Invalid Request"},
                },
                400,
            )
            return

        mid = req.get("id", 1)
        method = req.get("method")
        params = req.get("params", []) or []
        ok = lambda result: self._send({"jsonrpc": "2.0", "id": mid, "result": result})
        err = lambda code, msg: self._send(
            {"jsonrpc": "2.0", "id": mid, "error": {"code": code, "message": msg}}, 200
        )

        try:
            with lock:
                if method in ("eth_blockNumber", "animica_blockNumber"):
                    ok(hex(state["height"]))
                    return
                if method in ("net_version", "eth_chainId", "animica_chainId"):
                    ok(state["chain_id"])
                    return
                if method in ("getblockcount", "animica_blockCount"):
                    ok(state["height"])
                    return
                if method == "evm_mine":
                    n = 1
                    if params:
                        v = params[0]
                        try:
                            if isinstance(v, int):
                                n = v
                            elif isinstance(v, str) and v.startswith("0x"):
                                n = int(v, 16)
                            else:
                                n = int(v)
                        except Exception:
                            n = 1
                    n = max(1, n)
                    state["height"] += n
                    save_state()
                    ok(hex(state["height"]))
                    return
                if method == "animica_generate":
                    n = 1
                    if params:
                        try:
                            n = int(params[0])
                        except Exception:
                            try:
                                n = int(str(params[0]), 16)
                            except Exception:
                                n = 1
                    n = max(1, n)
                    state["height"] += n
                    save_state()
                    ok({"height": state["height"], "mined": n})
                    return
                if method == "miner_start":
                    state["auto_mine"] = True
                    ok(True)
                    return
                if method == "miner_stop":
                    state["auto_mine"] = False
                    ok(True)
                    return
                if method in ("animica_status", "getblockchaininfo"):
                    ok(
                        {
                            "height": state["height"],
                            "chainId": state["chain_id"],
                            "autoMine": state["auto_mine"],
                        }
                    )
                    return
                if method == "eth_getBlockByNumber":
                    tag = params[0] if params else "latest"
                    if isinstance(tag, str):
                        if tag in ("latest", "finalized", "safe", "pending"):
                            n = state["height"]
                        elif tag == "earliest":
                            n = 0
                        elif tag.startswith("0x"):
                            n = int(tag, 16)
                        else:
                            n = int(tag)
                    else:
                        n = state["height"]
                    ok(make_block(max(0, n)))
                    return
                if method in ("getblock", "animica_getBlock"):
                    try:
                        n = int(params[0]) if params else state["height"]
                    except Exception:
                        try:
                            n = int(str(params[0]), 16)
                        except Exception:
                            n = state["height"]
                    ok(make_block(max(0, n)))
                    return
                if method == "getblockhash":
                    try:
                        n = int(params[0]) if params else state["height"]
                    except Exception:
                        try:
                            n = int(str(params[0]), 16)
                        except Exception:
                            n = state["height"]
                    ok(make_block(max(0, n))["hash"])
                    return
                if method == "web3_clientVersion":
                    ok("animica-dev/0.0.0 (test-stub, not for production)")
                    return
        except Exception as exc:
            self._send(
                {
                    "jsonrpc": "2.0",
                    "id": mid,
                    "error": {"code": -32000, "message": str(exc)},
                },
                500,
            )
            return

        err(-32601, "Method not found")


def auto_miner():
    while True:
        time.sleep(1)
        with lock:
            if state["auto_mine"]:
                state["height"] += 1
                save_state()


def parse_args():
    p = argparse.ArgumentParser()
    p.add_argument("--network", default="testnet")
    p.add_argument("--chain-id", dest="chain_id", default="0xa11ca")
    p.add_argument("--datadir", default=str(Path.home() / ".animica" / "testnet"))
    p.add_argument("--rpc-addr", default="0.0.0.0")
    p.add_argument("--rpc-port", type=int, default=8545)
    p.add_argument("--ws-port", type=int, default=8546)
    p.add_argument("--p2p-port", type=int, default=30333)
    p.add_argument("--http", action="store_true")
    p.add_argument("--ws", action="store_true")
    p.add_argument("--allow-cors", action="store_true")
    p.add_argument("--auto-mine", action="store_true")
    p.add_argument("--nat", default=None, help="public IP (accepted & ignored by shim)")
    return p.parse_args()


def main():
    """
    Start the stub RPC server.
    
    **TEST-ONLY**: This is a development/testing server with synthetic blocks.
    DO NOT use in production. For production, use the real node implementation
    with full consensus, execution, and persistence layers.
    """
    args = parse_args()
    
    # Warning for production detection
    if os.getenv("ANIMICA_NETWORK") == "mainnet" or os.getenv("ANIMICA_ENV") == "production":
        print(
            "ERROR: AICF stub node cannot be used with mainnet or production settings.",
            file=sys.stderr
        )
        print(
            "This is a test-only stub. Use the real node implementation for production.",
            file=sys.stderr
        )
        os._exit(1)
    
    with lock:
        state["chain_id"] = args.chain_id
        state["auto_mine"] = bool(args.auto_mine)
    load_state(args.datadir)
    t = threading.Thread(target=auto_miner, daemon=True)
    t.start()
    httpd = ThreadingHTTPServer((args.rpc_addr, args.rpc_port), Handler)
    for s in (signal.SIGINT, signal.SIGTERM):
        signal.signal(s, lambda *_: os._exit(0))
    print(
        f"[stub-node] TEST-ONLY RPC server at http://{args.rpc_addr}:{args.rpc_port}"
    )
    print(
        f"[stub-node] chainId={state['chain_id']} height={state['height']} (synthetic blocks)"
    )
    httpd.serve_forever()


if __name__ == "__main__":
    main()
