from __future__ import annotations
import asyncio
import json
import logging
import platform
import socket
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from starlette.responses import StreamingResponse

logger = logging.getLogger(__name__)

TEMPLATE_DIR = Path(__file__).parent / "templates"


VERSION = "1.0.0"


def _get_uptime() -> str:
    """Get system uptime as a human-readable string."""
    try:
        with open("/proc/uptime", "r") as f:
            uptime_seconds = float(f.readline().split()[0])
        days = int(uptime_seconds // 86400)
        hours = int((uptime_seconds % 86400) // 3600)
        minutes = int((uptime_seconds % 3600) // 60)
        parts = []
        if days:
            parts.append(f"{days}d")
        if hours:
            parts.append(f"{hours}h")
        parts.append(f"{minutes}m")
        return " ".join(parts)
    except Exception:
        return "unknown"


def create_dashboard_app(controller_url: str = "http://localhost:8401") -> FastAPI:
    app = FastAPI(title="Mesh Optimizer Dashboard")
    templates = Jinja2Templates(directory=str(TEMPLATE_DIR))

    dashboard_state = {
        "controller_url": controller_url,
        "nat_mode": False,
    }

    async def _proxy_get(path: str) -> dict:
        """Proxy a GET request to the mesh controller."""
        import aiohttp
        url = f"{dashboard_state['controller_url']}{path}"
        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
                    if resp.status == 200:
                        return await resp.json()
                    return {"error": f"Controller returned {resp.status}"}
        except Exception as e:
            logger.warning("Controller unreachable at %s: %s", url, e)
            return {"error": str(e)}

    async def _proxy_post(path: str, data: Optional[dict] = None) -> dict:
        """Proxy a POST request to the mesh controller."""
        import aiohttp
        url = f"{dashboard_state['controller_url']}{path}"
        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(url, json=data, timeout=aiohttp.ClientTimeout(total=10)) as resp:
                    if resp.status in (200, 201, 202):
                        return await resp.json()
                    return {"error": f"Controller returned {resp.status}"}
        except Exception as e:
            logger.warning("Controller unreachable at %s: %s", url, e)
            return {"error": str(e)}

    # ── HTML routes ──────────────────────────────────────────────

    @app.get("/", response_class=HTMLResponse)
    async def index(request: Request):
        nodes = await _proxy_get("/nodes")
        return templates.TemplateResponse("index.html", {
            "request": request,
            "nodes": nodes if isinstance(nodes, list) else [],
        })

    @app.get("/partials/node-cards", response_class=HTMLResponse)
    async def node_cards_partial(request: Request):
        nodes = await _proxy_get("/nodes")
        return templates.TemplateResponse("partials/node_cards.html", {
            "request": request,
            "nodes": nodes if isinstance(nodes, list) else [],
        })

    @app.get("/node/{node_id}", response_class=HTMLResponse)
    async def node_detail(request: Request, node_id: str):
        node = await _proxy_get(f"/nodes/{node_id}")
        return templates.TemplateResponse("node_detail.html", {
            "request": request,
            "node": node if isinstance(node, dict) and "error" not in node else {},
            "node_id": node_id,
        })

    @app.get("/jepa", response_class=HTMLResponse)
    async def jepa_page(request: Request):
        stats = await _proxy_get("/jepa/stats")
        schedule = await _proxy_get("/jepa/schedule")
        interval_s = schedule.get("interval_s", 21600) if isinstance(schedule, dict) else 21600

        # Convert interval to human-readable display
        if interval_s >= 604800 and interval_s % 604800 == 0:
            schedule_value = int(interval_s / 604800)
            schedule_unit = "weeks"
            schedule_display = f"{schedule_value} week(s)"
        elif interval_s >= 86400 and interval_s % 86400 == 0:
            schedule_value = int(interval_s / 86400)
            schedule_unit = "days"
            schedule_display = f"{schedule_value} day(s)"
        else:
            schedule_value = max(1, int(interval_s / 3600))
            schedule_unit = "hours"
            schedule_display = f"{schedule_value} hour(s)"

        # Get training log
        training_log = []
        try:
            log_data = await _proxy_get("/jepa/training-log")
            if isinstance(log_data, list):
                training_log = log_data
        except Exception:
            pass

        return templates.TemplateResponse("jepa.html", {
            "request": request,
            "stats": stats if isinstance(stats, dict) and "error" not in stats else {},
            "schedule_value": schedule_value,
            "schedule_unit": schedule_unit,
            "schedule_display": schedule_display,
            "training_log": training_log,
        })

    @app.get("/jobs", response_class=HTMLResponse)
    async def jobs_page(request: Request):
        jobs = await _proxy_get("/jobs")
        nodes = await _proxy_get("/nodes")
        return templates.TemplateResponse("jobs.html", {
            "request": request,
            "jobs": jobs if isinstance(jobs, list) else [],
            "nodes": nodes if isinstance(nodes, list) else [],
        })

    @app.get("/atlas", response_class=HTMLResponse)
    async def atlas_page(request: Request):
        atlas = await _proxy_get("/atlas/summary")
        return templates.TemplateResponse("atlas.html", {
            "request": request,
            "atlas": atlas if isinstance(atlas, dict) and "error" not in atlas else {},
        })

    @app.get("/tuning", response_class=HTMLResponse)
    async def tuning_page(request: Request):
        nodes = await _proxy_get("/nodes")
        node_list = nodes if isinstance(nodes, list) else []
        # Fetch recommendations for each node
        tuning_data = {}
        for node in node_list:
            nid = node.get("node_id", "")
            rec = await _proxy_get(f"/nodes/{nid}/recommendations")
            if isinstance(rec, dict) and "error" not in rec:
                tuning_data[nid] = rec
        return templates.TemplateResponse("tuning.html", {
            "request": request,
            "nodes": node_list,
            "tuning": tuning_data,
        })

    @app.get("/settings", response_class=HTMLResponse)
    async def settings_page(request: Request):
        # Fetch license info from controller
        license_info = await _proxy_get("/api/licensing/status")
        if not isinstance(license_info, dict) or "error" in license_info:
            license_info = {
                "tier": "community",
                "valid": False,
                "license_key_set": False,
                "masked_key": "None",
                "current_nodes": 0,
                "max_nodes": 0,
                "expires_at": None,
                "days_left": None,
            }

        # Mask the key if present but not already masked
        if license_info.get("license_key") and not license_info.get("masked_key"):
            key = license_info["license_key"]
            license_info["masked_key"] = key[:5] + "****-****-" + key[-4:] if len(key) > 9 else "****"

        ctrl_url = dashboard_state["controller_url"]
        license_key = license_info.get("license_key", "") or ""

        install_commands = {
            "linux": f'curl -fsSL https://mesh.slentosystems.com/install.sh | bash -s -- --license {license_key or "YOUR_KEY"} --controller {ctrl_url} --nat',
            "macos": f'curl -fsSL https://mesh.slentosystems.com/installers/macos/install.sh | bash -s -- --license {license_key or "YOUR_KEY"} --controller {ctrl_url} --nat',
            "windows": f'irm https://mesh.slentosystems.com/installers/windows/install.ps1 -OutFile install.ps1; .\\install.ps1 -LicenseKey "{license_key or "YOUR_KEY"}" -ControllerUrl "{ctrl_url}" -NatMode',
        }

        agent_info = {
            "hostname": socket.gethostname(),
            "node_id": license_info.get("node_id", socket.gethostname()),
            "port": "8402",
        }

        return templates.TemplateResponse("settings.html", {
            "request": request,
            "license": license_info,
            "controller_url": ctrl_url,
            "nat_mode": dashboard_state.get("nat_mode", False),
            "agent": agent_info,
            "install_commands": install_commands,
        })

    @app.get("/about", response_class=HTMLResponse)
    async def about_page(request: Request):
        license_info = await _proxy_get("/api/licensing/status")
        if not isinstance(license_info, dict) or "error" in license_info:
            license_info = {"tier": "community", "valid": False}

        return templates.TemplateResponse("about.html", {
            "request": request,
            "version": VERSION,
            "build_info": "release",
            "python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
            "platform_info": platform.platform(),
            "license": license_info,
            "hostname": socket.gethostname(),
            "os_info": f"{platform.system()} {platform.release()}",
            "uptime": _get_uptime(),
        })

    @app.post("/api/settings/controller-url")
    async def api_update_controller_url(request: Request):
        data = await request.json()
        new_url = data.get("controller_url", "").strip().rstrip("/")
        if not new_url:
            return JSONResponse({"status": "error", "message": "controller_url is required"}, status_code=400)
        dashboard_state["controller_url"] = new_url
        return {"status": "ok", "message": f"Controller URL updated to {new_url}"}

    @app.post("/api/settings/license")
    async def api_activate_license(request: Request):
        data = await request.json()
        license_key = data.get("license_key", "").strip()
        if not license_key:
            return JSONResponse({"status": "error", "message": "license_key is required"}, status_code=400)
        result = await _proxy_post("/api/licensing/activate", {"license_key": license_key})
        if isinstance(result, dict) and "error" in result:
            return JSONResponse({"status": "error", "message": result["error"]}, status_code=502)
        return {"status": "ok", "message": "License activated successfully", "data": result}

    @app.delete("/api/settings/license")
    async def api_deactivate_license(request: Request):
        result = await _proxy_post("/api/licensing/deactivate")
        if isinstance(result, dict) and "error" in result:
            return JSONResponse({"status": "error", "message": result["error"]}, status_code=502)
        return {"status": "ok", "message": "Reverted to community mode", "data": result}

    @app.post("/api/settings/nat-mode")
    async def api_toggle_nat_mode(request: Request):
        dashboard_state["nat_mode"] = not dashboard_state.get("nat_mode", False)
        new_state = dashboard_state["nat_mode"]
        return {"status": "ok", "message": f"NAT mode {'enabled' if new_state else 'disabled'}", "nat_mode": new_state}

    @app.post("/api/jepa/schedule")
    async def api_jepa_schedule(request: Request):
        data = await request.json()
        interval_s = data.get("interval_s")
        if not interval_s:
            return {"error": "interval_s required"}
        return await _proxy_post(f"/jepa/schedule?interval_s={interval_s}")

    @app.delete("/api/nodes/{node_id}")
    async def api_delete_node(request: Request, node_id: str, archive: bool = False):
        import aiohttp
        url = f"{dashboard_state['controller_url']}/nodes/{node_id}?archive={str(archive).lower()}"
        try:
            async with aiohttp.ClientSession() as session:
                async with session.delete(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
                    return await resp.json()
        except Exception as e:
            return {"error": str(e)}

    @app.post("/api/tuning/apply")
    async def api_apply_tuning(request: Request):
        data = await request.json()
        node_id = data.get("node_id")
        if not node_id:
            return {"error": "node_id required"}
        # Fetch the node to get its agent URL
        nodes = await _proxy_get("/nodes")
        node_list = nodes if isinstance(nodes, list) else []
        agent_url = None
        for n in node_list:
            if n.get("node_id") == node_id:
                agent_url = n.get("agent_url")
                break
        if not agent_url:
            return {"error": f"Node {node_id} not found"}
        # Tell the agent to apply tuning
        import aiohttp
        try:
            url = f"{agent_url}/tuning/apply"
            async with aiohttp.ClientSession() as session:
                async with session.post(url, json=data,
                                         timeout=aiohttp.ClientTimeout(total=30)) as resp:
                    return await resp.json()
        except Exception as e:
            return {"error": str(e)}

    # ── SSE endpoint ─────────────────────────────────────────────

    @app.get("/api/sse")
    async def sse_stream(request: Request):
        async def event_generator():
            while True:
                if await request.is_disconnected():
                    break
                nodes = await _proxy_get("/nodes")
                data = json.dumps({
                    "timestamp": datetime.utcnow().isoformat(),
                    "nodes": nodes if isinstance(nodes, list) else [],
                })
                yield f"event: health\ndata: {data}\n\n"
                await asyncio.sleep(5)

        return StreamingResponse(
            event_generator(),
            media_type="text/event-stream",
            headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
        )

    # ── JSON API routes ──────────────────────────────────────────

    @app.get("/api/nodes")
    async def api_nodes():
        return await _proxy_get("/nodes")

    @app.get("/api/jepa/stats")
    async def api_jepa_stats():
        return await _proxy_get("/jepa/stats")

    @app.post("/api/jepa/retrain")
    async def api_jepa_retrain():
        return await _proxy_post("/jepa/retrain")

    @app.post("/api/jobs/submit")
    async def api_jobs_submit(request: Request):
        data = await request.json()
        return await _proxy_post("/jobs/submit", data)

    return app


# Default app instance for `uvicorn mesh.dashboard.app:app`
app = create_dashboard_app()
