#!/usr/bin/env python3
"""Local, read-only Kraken market and portfolio dashboard."""
from __future__ import annotations

import base64
import hashlib
import hmac
import json
import os
import threading
import time
import urllib.parse
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from paper_trader import PaperTrader

HOST = os.getenv("KRAKEN_DASHBOARD_HOST", "127.0.0.1")
PORT = int(os.getenv("KRAKEN_DASHBOARD_PORT", "8765"))
PAIRS = [p.strip().upper() for p in os.getenv("KRAKEN_PAIRS", "XBT/USD,ETH/USD,SOL/USD").split(",") if p.strip()]
ROOT = Path(__file__).parent
PAPER_TRADER = PaperTrader(cash=10_000, fast_window=5, slow_window=20, allocation=0.25)


def kraken_request(path: str, params: dict[str, str] | None = None) -> dict:
    query = urllib.parse.urlencode(params or {})
    url = f"https://api.kraken.com{path}" + (f"?{query}" if query else "")
    request = urllib.request.Request(url, headers={"User-Agent": "local-kraken-dashboard/1.0"})
    with urllib.request.urlopen(request, timeout=15) as response:
        payload = json.loads(response.read().decode("utf-8"))
    if payload.get("error"):
        raise RuntimeError("; ".join(payload["error"]))
    return payload["result"]


def private_kraken_request(path: str, params: dict[str, str] | None = None) -> dict:
    key = os.getenv("KRAKEN_API_KEY")
    secret = os.getenv("KRAKEN_API_SECRET")
    if not key or not secret:
        raise RuntimeError("Kraken API credentials are not configured")
    data = {"nonce": str(time.time_ns() // 1_000_000), **(params or {})}
    encoded = urllib.parse.urlencode(data)
    message = path.encode() + hashlib.sha256((data["nonce"] + encoded).encode()).digest()
    signature = hmac.new(base64.b64decode(secret), message, hashlib.sha512).digest()
    request = urllib.request.Request(
        f"https://api.kraken.com{path}",
        data=encoded.encode(),
        headers={"API-Key": key, "API-Sign": base64.b64encode(signature).decode(), "User-Agent": "local-kraken-dashboard/1.0"},
    )
    with urllib.request.urlopen(request, timeout=15) as response:
        payload = json.loads(response.read().decode("utf-8"))
    if payload.get("error"):
        raise RuntimeError("; ".join(payload["error"]))
    return payload["result"]


def normalize_pair(pair: str) -> str:
    return pair.replace("/", "").replace("BTC", "XBT")


def public_snapshot() -> dict:
    assets = []
    for requested in PAIRS:
        ticker = kraken_request("/0/public/Ticker", {"pair": normalize_pair(requested)})
        values = next(iter(ticker.values()))
        assets.append({
            "pair": requested,
            "last": float(values["c"][0]),
            "bid": float(values["b"][0]),
            "ask": float(values["a"][0]),
            "high": float(values["h"][1]),
            "low": float(values["l"][1]),
            "volume": float(values["v"][1]),
            "vwap": float(values["p"][1]),
        })
        PAPER_TRADER.step(requested, assets[-1]["last"])
    return {"updated": int(time.time()), "tickers": assets}


def paper_snapshot() -> dict:
    market = public_snapshot()
    return PAPER_TRADER.snapshot({ticker["pair"]: ticker["last"] for ticker in market["tickers"]})


def portfolio_snapshot() -> dict:
    balances = private_kraken_request("/0/private/Balance")
    return {"configured": True, "balances": [{"asset": key, "amount": float(value)} for key, value in balances.items() if float(value) > 0]}


class Handler(BaseHTTPRequestHandler):
    def log_message(self, format: str, *args) -> None:
        return

    def send_json(self, payload: dict, status: int = 200) -> None:
        body = json.dumps(payload).encode()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Cache-Control", "no-store")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self) -> None:
        if self.path == "/api/market":
            try:
                self.send_json(public_snapshot())
            except Exception as exc:
                self.send_json({"error": str(exc)}, 502)
            return
        if self.path == "/api/portfolio":
            try:
                self.send_json(portfolio_snapshot())
            except RuntimeError as exc:
                self.send_json({"configured": False, "error": str(exc)})
            except Exception as exc:
                self.send_json({"configured": True, "error": str(exc)}, 502)
            return
        if self.path == "/api/paper":
            try:
                self.send_json(paper_snapshot())
            except Exception as exc:
                self.send_json({"error": str(exc)}, 502)
            return
        if self.path in ("/", "/index.html"):
            body = (ROOT / "kraken_dashboard.html").read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        self.send_error(404)


if __name__ == "__main__":
    server = ThreadingHTTPServer((HOST, PORT), Handler)
    print(f"Kraken dashboard: http://{HOST}:{PORT}")
    print(f"Watching: {', '.join(PAIRS)}")
    server.serve_forever()
