import asyncio
import os
from typing import Optional

from rich import box
from rich.console import Console
from rich.layout import Layout
from rich.panel import Panel
from rich.prompt import Prompt, IntPrompt
from rich.table import Table
from rich.text import Text

from .sdk import EmpoorioChainConfig, EmpoorioChainSDK, Transaction

console = Console()


def _env(name: str, default: Optional[str] = None) -> Optional[str]:
    value = os.getenv(name, default)
    return value if value and value.strip() else default


def _build_config() -> EmpoorioChainConfig:
    rpc_url = _env("EMPOORIO_RPC_URL")
    if not rpc_url:
        raise ValueError("EMPOORIO_RPC_URL is required (e.g. http://HOST:8545)")
    ws_url = _env("EMPOORIO_WS_URL")
    chain_id = int(_env("EMPOORIO_CHAIN_ID", "1234"))
    return EmpoorioChainConfig(rpc_url=rpc_url, ws_url=ws_url, chain_id=chain_id)


async def _connect_sdk() -> EmpoorioChainSDK:
    sdk = EmpoorioChainSDK(_build_config())
    await sdk.connect()
    return sdk


async def _render_dashboard(sdk: EmpoorioChainSDK) -> None:
    node = await sdk.get_node_info()
    latest_block = await sdk.get_latest_block()
    gas_price = await sdk.get_gas_price()

    status = "SYNCING" if node.get("syncing") not in (False, None) else "READY"
    status_color = "yellow" if status == "SYNCING" else "green"

    left = Table(box=box.MINIMAL, expand=True)
    left.add_column("Field", style="cyan", no_wrap=True)
    left.add_column("Value", style="white")
    left.add_row("RPC", sdk.config.rpc_url)
    left.add_row("WS", sdk.config.ws_url or "not set")
    left.add_row("Chain ID", str(node.get("chain_id")))
    left.add_row("Network ID", str(node.get("network_id")))
    left.add_row("Peers", str(node.get("peer_count")))
    left.add_row("Status", f"[{status_color}]{status}[/{status_color}]")

    right = Table(box=box.MINIMAL, expand=True)
    right.add_column("Field", style="cyan", no_wrap=True)
    right.add_column("Value", style="white")
    right.add_row("Latest Block", str(latest_block.number))
    right.add_row("Block Hash", latest_block.hash)
    right.add_row("Txs", str(len(latest_block.transactions)))
    right.add_row("Gas Used", latest_block.gas_used)
    right.add_row("Gas Limit", latest_block.gas_limit)
    right.add_row("Gas Price", str(gas_price))

    layout = Layout()
    layout.split_column(
        Layout(Panel(Text("EmpoorioChain SDK Terminal", justify="center"), style="bold cyan"), size=3),
        Layout(name="body"),
    )
    layout["body"].split_row(Layout(Panel(left, title="Node"), name="left"), Layout(Panel(right, title="Chain"), name="right"))

    console.print(layout)


async def _get_balance(sdk: EmpoorioChainSDK) -> None:
    address = Prompt.ask("Address")
    balance = await sdk.get_balance(address)
    table = Table(title="Balance", box=box.SIMPLE)
    table.add_column("Address", style="cyan")
    table.add_column("Balance", style="green")
    table.add_row(balance.address, f"{balance.balance} {balance.symbol}")
    console.print(table)


async def _get_block(sdk: EmpoorioChainSDK) -> None:
    choice = Prompt.ask("Lookup by", choices=["number", "hash"], default="number")
    if choice == "number":
        number = IntPrompt.ask("Block number")
        block = await sdk.get_block(number)
    else:
        hash_str = Prompt.ask("Block hash (0x...)")
        block = await sdk.get_block_by_hash(hash_str)

    if not block:
        console.print("[red]Block not found[/red]")
        return

    table = Table(title=f"Block {block.number}", box=box.MINIMAL_DOUBLE_HEAD)
    table.add_column("Field", style="cyan", no_wrap=True)
    table.add_column("Value", style="white")
    table.add_row("Hash", block.hash)
    table.add_row("Parent", block.parent_hash)
    table.add_row("Timestamp", str(block.timestamp))
    table.add_row("Txs", str(len(block.transactions)))
    table.add_row("Gas Used", block.gas_used)
    table.add_row("Gas Limit", block.gas_limit)
    console.print(table)


async def _get_transaction(sdk: EmpoorioChainSDK) -> None:
    tx_hash = Prompt.ask("Transaction hash (0x...)")
    tx = await sdk.get_transaction(tx_hash)
    if not tx:
        console.print("[red]Transaction not found[/red]")
        return
    table = Table(title="Transaction", box=box.MINIMAL_DOUBLE_HEAD)
    table.add_column("Field", style="cyan", no_wrap=True)
    table.add_column("Value", style="white")
    for key in [
        "hash",
        "block_number",
        "from",
        "to",
        "value",
        "gas",
        "gas_price",
        "data",
        "nonce",
    ]:
        table.add_row(key, str(tx.get(key)))
    console.print(table)


async def _estimate_gas(sdk: EmpoorioChainSDK) -> None:
    from_addr = Prompt.ask("From address")
    to_addr = Prompt.ask("To address")
    value = Prompt.ask("Value (wei)", default="0")
    data = Prompt.ask("Data (0x...)", default="0x")
    gas = await sdk.estimate_gas(
        Transaction(
            from_address=from_addr,
            to_address=to_addr,
            value=value,
            data=data,
        )
    )
    console.print(f"[green]Estimated gas:[/green] {gas}")


async def _send_transaction(sdk: EmpoorioChainSDK) -> None:
    from_addr = Prompt.ask("From address")
    to_addr = Prompt.ask("To address")
    value = Prompt.ask("Value (wei)", default="0")
    gas = Prompt.ask("Gas (optional)", default="")
    gas_price = Prompt.ask("Gas price (optional)", default="")
    data = Prompt.ask("Data (0x...)", default="0x")
    private_key = Prompt.ask("Private key (leave blank to use external signer)", default="")

    tx = Transaction(
        from_address=from_addr,
        to_address=to_addr,
        value=value,
        gas=gas if gas else None,
        gas_price=gas_price if gas_price else None,
        data=data,
    )
    tx_hash = await sdk.send_transaction(tx, private_key if private_key else None)
    console.print(f"[green]Transaction sent:[/green] {tx_hash}")


async def _wait_for_tx(sdk: EmpoorioChainSDK) -> None:
    tx_hash = Prompt.ask("Transaction hash (0x...)")
    confirmations = IntPrompt.ask("Confirmations", default=1)
    receipt = await sdk.wait_for_transaction(tx_hash, confirmations=confirmations)
    console.print("[green]Confirmed[/green]")
    console.print(receipt)


async def _subscribe_blocks(sdk: EmpoorioChainSDK) -> None:
    console.print("[yellow]Subscribing to new blocks (Ctrl+C to stop)...[/yellow]")

    def on_block(block):
        console.print(f"New block #{block.number} {block.hash}")

    await sdk.subscribe_to_new_blocks(on_block)


async def _subscribe_pending(sdk: EmpoorioChainSDK) -> None:
    console.print("[yellow]Subscribing to pending transactions (Ctrl+C to stop)...[/yellow]")

    def on_tx(tx_hash):
        console.print(f"Pending tx {tx_hash}")

    await sdk.subscribe_to_pending_transactions(on_tx)


def main() -> None:
    console.print("[bold cyan]EmpoorioChain SDK Terminal[/bold cyan]")
    try:
        sdk = asyncio.run(_connect_sdk())
    except Exception as exc:
        console.print(f"[red]Failed to connect:[/red] {exc}")
        raise SystemExit(1)

    try:
        asyncio.run(_render_dashboard(sdk))
        while True:
            console.print()
            console.print("[bold]Menu[/bold]")
            console.print("1) Dashboard")
            console.print("2) Node info")
            console.print("3) Get balance")
            console.print("4) Get block")
            console.print("5) Get transaction")
            console.print("6) Estimate gas")
            console.print("7) Send transaction")
            console.print("8) Wait for transaction")
            console.print("9) Subscribe new blocks")
            console.print("10) Subscribe pending txs")
            console.print("0) Exit")
            choice = Prompt.ask("Select", choices=[str(i) for i in range(11)], default="1")

            if choice == "0":
                break
            if choice == "1":
                asyncio.run(_render_dashboard(sdk))
            elif choice == "2":
                node = asyncio.run(sdk.get_node_info())
                table = Table(title="Node Info", box=box.SIMPLE)
                table.add_column("Field", style="cyan")
                table.add_column("Value", style="white")
                for key, value in node.items():
                    table.add_row(str(key), str(value))
                console.print(table)
            elif choice == "3":
                asyncio.run(_get_balance(sdk))
            elif choice == "4":
                asyncio.run(_get_block(sdk))
            elif choice == "5":
                asyncio.run(_get_transaction(sdk))
            elif choice == "6":
                asyncio.run(_estimate_gas(sdk))
            elif choice == "7":
                asyncio.run(_send_transaction(sdk))
            elif choice == "8":
                asyncio.run(_wait_for_tx(sdk))
            elif choice == "9":
                try:
                    asyncio.run(_subscribe_blocks(sdk))
                except KeyboardInterrupt:
                    console.print("\n[yellow]Stopped[/yellow]")
            elif choice == "10":
                try:
                    asyncio.run(_subscribe_pending(sdk))
                except KeyboardInterrupt:
                    console.print("\n[yellow]Stopped[/yellow]")
    finally:
        try:
            asyncio.run(sdk.disconnect())
        except Exception:
            pass


if __name__ == "__main__":
    main()
