"""Dashboard API: web panel + CLI endpoints for managing accounts, VMs, and costs.

Mounted at /dashboard/ on the main proxy app.
"""

from __future__ import annotations

from pathlib import Path

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

from proxym.accounts import (
    Account,
    AccountCredentials,
    AccountLimits,
    AccountManager,
    ProviderType,
    ToolBinding,
    ToolType,
)
from proxym.config import get_settings
from proxym.dashboard.tickets import (
    Sprint,
    SprintStatus,
    Ticket,
    TicketComment,
    TicketManager,
    TicketPriority,
    TicketStatus,
    TicketType,
    ToolAssignment,
)
from proxym.providers import MODELS
from proxym.virt import VMConfig, VMOrchestrator

router = APIRouter(prefix="/dashboard", tags=["dashboard"])

# Singletons (initialized on first use)
_acct_mgr: AccountManager | None = None
_vm_orch: VMOrchestrator | None = None
_ticket_mgr: TicketManager | None = None


def _accounts() -> AccountManager:
    global _acct_mgr
    if _acct_mgr is None:
        _acct_mgr = AccountManager()
    return _acct_mgr


def _vms() -> VMOrchestrator:
    global _vm_orch
    if _vm_orch is None:
        _vm_orch = VMOrchestrator()
    return _vm_orch


def _tickets() -> TicketManager:
    global _ticket_mgr
    if _ticket_mgr is None:
        from pathlib import Path
        persist = Path.home() / ".local" / "share" / "proxym" / "tickets.json"
        _ticket_mgr = TicketManager(persist_path=persist)
    return _ticket_mgr


# ── Pydantic request models ──────────────────────────────────

class CreateAccountReq(BaseModel):
    name: str
    provider: str
    email: str = ""
    plan_tier: str = ""
    api_key: str = ""
    tags: list[str] = []
    daily_budget_usd: float = 0
    monthly_budget_usd: float = 0


class BindToolReq(BaseModel):
    tool_type: str
    tool_name: str
    vm_id: str = ""
    project_path: str = ""
    browser_profile: str = ""


class CreateVMReq(BaseModel):
    name: str
    account_id: str = ""
    tool: str = ""
    vcpus: int = 2
    memory_mb: int = 4096
    disk_gb: int = 20
    base_image: str = "fedora-40-dev"
    code_mount_host: str = ""


class SwitchProjectReq(BaseModel):
    project_path: str


class CreateTicketReq(BaseModel):
    title: str
    description: str = ""
    status: str = "todo"
    priority: str = "medium"
    type: str = "task"
    sprint_id: str = ""
    tags: list[str] = []
    files: list[str] = []
    estimated_hours: float = 0


class UpdateTicketReq(BaseModel):
    title: str | None = None
    description: str | None = None
    status: str | None = None
    priority: str | None = None
    type: str | None = None
    sprint_id: str | None = None
    tags: list[str] | None = None
    files: list[str] | None = None
    estimated_hours: float | None = None
    actual_hours: float | None = None


class AssignToolReq(BaseModel):
    tool: str  # vscode, aider, claude-code, windsurf, cursor
    agent_mode: str = ""
    model: str = ""


class AddCommentReq(BaseModel):
    author: str = ""
    content: str


class CreateSprintReq(BaseModel):
    name: str
    goal: str = ""
    start_date: str = ""
    end_date: str = ""


class UpdateSprintReq(BaseModel):
    name: str | None = None
    goal: str | None = None
    status: str | None = None
    start_date: str | None = None
    end_date: str | None = None


# ── Account endpoints ────────────────────────────────────────

@router.get("/accounts")
async def list_accounts(provider: str | None = None, tag: str | None = None):
    mgr = _accounts()
    prov = ProviderType(provider) if provider else None
    accts = mgr.list_accounts(provider=prov, tag=tag)
    return {"accounts": [a.status_summary() for a in accts]}


@router.post("/accounts")
async def create_account(req: CreateAccountReq):
    mgr = _accounts()
    acct = Account(
        name=req.name,
        provider=ProviderType(req.provider),
        email=req.email,
        plan_tier=req.plan_tier,
        credentials=AccountCredentials(api_key=req.api_key),
        limits=AccountLimits(
            user_daily_budget_usd=req.daily_budget_usd,
            user_monthly_budget_usd=req.monthly_budget_usd,
        ),
        tags=req.tags,
    )
    mgr.add_account(acct)
    return acct.status_summary()


@router.get("/accounts/{account_id}")
async def get_account(account_id: str):
    acct = _accounts().get_account(account_id)
    if not acct:
        raise HTTPException(404, "Account not found")
    return acct.status_summary()


@router.delete("/accounts/{account_id}")
async def delete_account(account_id: str):
    if _accounts().remove_account(account_id):
        return {"ok": True}
    raise HTTPException(404, "Account not found")


@router.post("/accounts/{account_id}/bind-tool")
async def bind_tool(account_id: str, req: BindToolReq):
    binding = ToolBinding(
        tool_type=ToolType(req.tool_type),
        tool_name=req.tool_name,
        vm_id=req.vm_id,
        project_path=req.project_path,
        browser_profile=req.browser_profile,
    )
    if _accounts().bind_tool(account_id, binding):
        return {"ok": True}
    raise HTTPException(404, "Account not found")


# ── Cost / usage endpoints ───────────────────────────────────

@router.get("/costs")
async def cost_summary():
    """Aggregate cost dashboard across all accounts."""
    return _accounts().get_cost_summary()


@router.get("/costs/detailed")
async def cost_detailed():
    """Per-account cost breakdown."""
    mgr = _accounts()
    accounts = mgr.list_accounts()
    rows = []
    for a in accounts:
        rows.append({
            "id": a.id,
            "name": a.name,
            "provider": a.provider.value,
            "plan": a.plan_tier,
            "daily_cost": round(a.usage.daily_cost_usd, 4),
            "monthly_cost": round(a.usage.total_cost_usd, 4),
            "daily_requests": a.usage.daily_requests,
            "total_requests": a.usage.total_requests,
            "budget": a.limits.budget_remaining(a.usage),
            "over_budget": a.is_over_budget,
        })
    return {"accounts": rows, "summary": mgr.get_cost_summary()}


# ── Tool profiles ────────────────────────────────────────────

@router.get("/tools")
async def list_tools():
    """Return available VM tool profiles (host/browser environments)."""
    from proxym.virt.profiles import list_builtin_profiles, get_builtin_profile, PROFILES_DIR
    import yaml

    tools = []
    for name in list_builtin_profiles():
        profile_path = get_builtin_profile(name)
        info = {"name": name, "profile_exists": profile_path is not None}
        if profile_path:
            try:
                data = yaml.safe_load(profile_path.read_text())
                info["memory"] = data.get("memory", 4096)
                info["vcpus"] = data.get("vcpus", 2)
                info["base_image"] = data.get("base_image", "ubuntu-24.04")
                info["gui"] = data.get("gui", False)
                info["apps"] = data.get("apps", [])
                info["packages"] = len(data.get("packages", []))
            except Exception:
                pass
        tools.append(info)
    return {"tools": tools}


# ── VM endpoints ─────────────────────────────────────────────

@router.get("/vms")
async def list_vms():
    return {"vms": [
        {
            "id": s.id, "name": s.name, "state": s.state.value,
            "account_id": s.account_id, "tool": s.tool,
            "uptime": s.uptime_seconds, "ip": s.ip_address,
            "code_mount": s.code_mount,
            # Resources
            "memory_mb": s.memory_mb,
            "vcpus": s.vcpus,
            "disk_gb": s.disk_gb,
            # Timing
            "last_started_at": s.last_started_at,
            "created_at": s.created_at,
        }
        for s in _vms().list_vms()
    ]}


@router.post("/vms")
async def create_vm(req: CreateVMReq):
    config = VMConfig(
        name=req.name,
        account_id=req.account_id,
        tool=req.tool,
        vcpus=req.vcpus,
        memory_mb=req.memory_mb,
        disk_gb=req.disk_gb,
        base_image=req.base_image,
        code_mount_host=req.code_mount_host,
    )
    vm = _vms().create_vm(config)
    return {"id": vm.id, "name": vm.name, "state": vm.state.value}


@router.post("/vms/{vm_id}/start")
async def start_vm(vm_id: str):
    vms = _vms()
    vm = vms.vms.get(vm_id)
    if not vm:
        raise HTTPException(404, f"VM '{vm_id}' not found")

    libvirt = VMOrchestrator.check_libvirt_available()
    if not libvirt.get("libvirt"):
        raise HTTPException(
            503,
            "Libvirt is not available on this host. VM actions require virsh/libvirt to be installed and running.",
        )

    try:
        success = vms.start_vm(vm_id)
        if success:
            return {"ok": True, "vm_id": vm_id, "state": "running"}
        backend = getattr(vm, "backend", "unknown")
        raise HTTPException(
            500,
            f"Failed to start VM '{vm_id}' (backend={backend}). "
            "Check server logs for details.",
        )
    except RuntimeError as e:
        raise HTTPException(
            409,
            {
                "error": str(e),
                "hint": "Delete and re-create the VM (config exists, libvirt domain missing).",
            },
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(500, f"Failed to start VM: {e}")


@router.post("/vms/{vm_id}/stop")
async def stop_vm(vm_id: str, force: bool = False):
    if _vms().stop_vm(vm_id, force=force):
        return {"ok": True}
    raise HTTPException(500, "Failed to stop VM")


@router.post("/vms/{vm_id}/pause")
async def pause_vm(vm_id: str):
    if _vms().pause_vm(vm_id):
        return {"ok": True}
    raise HTTPException(500, "Failed to pause VM")


@router.post("/vms/{vm_id}/resume")
async def resume_vm(vm_id: str):
    if _vms().resume_vm(vm_id):
        return {"ok": True}
    raise HTTPException(500, "Failed to resume VM")


@router.post("/vms/{vm_id}/snapshot")
async def snapshot_vm(vm_id: str, name: str = ""):
    if _vms().snapshot_vm(vm_id, name):
        return {"ok": True}
    raise HTTPException(500, "Failed to create snapshot")


@router.post("/vms/{vm_id}/switch-project")
async def switch_project(vm_id: str, req: SwitchProjectReq):
    if _vms().switch_project(vm_id, req.project_path):
        return {"ok": True}
    raise HTTPException(500, "Failed to switch project")


@router.delete("/vms/{vm_id}")
async def delete_vm(vm_id: str):
    if _vms().delete_vm(vm_id):
        return {"ok": True}
    raise HTTPException(404, "VM not found")


@router.post("/vms/{vm_id}/delete")
async def delete_vm_post(vm_id: str):
    """POST variant for single VM deletion (matches frontend convention)."""
    if _vms().delete_vm(vm_id):
        return {"ok": True}
    raise HTTPException(404, "VM not found")


@router.post("/vms/bulk-delete")
async def bulk_delete_vms(req: list[str]):
    """Delete multiple VMs by their IDs. Returns list of successfully deleted VM IDs."""
    deleted = []
    failed = []
    for vm_id in req:
        try:
            if _vms().delete_vm(vm_id):
                deleted.append(vm_id)
            else:
                failed.append(vm_id)
        except Exception:
            failed.append(vm_id)
    return {"deleted": deleted, "failed": failed, "total_requested": len(req)}


# ── System status ────────────────────────────────────────────

@router.get("/")
async def dashboard_root():
    """Dashboard root - redirects to system status."""
    return await system_status()

@router.get("/system")
async def system_status():
    """Full system overview: accounts + VMs + costs + libvirt + providers."""
    settings = get_settings()
    libvirt = VMOrchestrator.check_libvirt_available()
    configured = settings.available_providers()
    return {
        "accounts": _accounts().get_cost_summary(),
        "vms": {
            "total": len(_vms().vms),
            "running": sum(1 for v in _vms().vms.values() if v.state.value == "running"),
        },
        "libvirt": libvirt,
        "providers": {
            "configured": list(configured.keys()),
            "total_models": sum(
                1 for m in MODELS.values() if m.provider in configured
            ),
        },
    }


@router.get("/providers")
async def list_providers():
    """Show configured providers from .env and their model counts."""
    settings = get_settings()
    configured = settings.available_providers()
    providers = []
    for name, key in configured.items():
        model_count = sum(1 for m in MODELS.values() if m.provider == name)
        # Ollama doesn't require API key (local service)
        is_active = name == "ollama" or (name != "ollama" and bool(key))
        providers.append({
            "name": name,
            "configured": True,
            "has_key": is_active,
            "model_count": model_count,
        })
    return {"providers": providers}


@router.get("/projects")
async def list_available_projects():
    """List projects from vscode_project_path with metadata."""
    settings = get_settings()
    root = Path(settings.vscode_project_path).expanduser() if settings.vscode_project_path else Path.home() / "projects"

    if not root.exists():
        return {"projects": [], "root": str(root), "exists": False}

    projects = []
    for p in sorted(root.iterdir()):
        if p.is_dir() and not p.name.startswith("."):
            projects.append({
                "name": p.name,
                "path": str(p),
                "has_git": (p / ".git").exists(),
                "has_pyproject": (p / "pyproject.toml").exists(),
                "has_package_json": (p / "package.json").exists(),
            })

    return {"projects": projects, "root": str(root), "exists": True, "count": len(projects)}


@router.get("/logs")
async def get_logs(lines: int = 50, level: str | None = None):
    """Get recent log entries from proxym log file."""
    from pathlib import Path
    import time
    
    log_paths = [
        Path.home() / ".local/share/proxym/proxym.log",
        Path("/var/log/proxym/proxym.log"),
        Path("./proxym.log"),
    ]
    
    log_file = None
    for p in log_paths:
        if p.exists():
            log_file = p
            break
    
    if not log_file:
        return {"logs": [], "error": "No log file found"}
    
    try:
        with open(log_file, "r") as f:
            all_lines = f.readlines()
        
        # Get last N lines
        recent = all_lines[-lines:]
        
        logs = []
        for line in recent:
            line = line.strip()
            if not line:
                continue
            
            # Parse structured log lines (timestamp level message)
            parts = line.split(" ", 2)
            if len(parts) >= 2:
                timestamp = parts[0] if len(parts) > 0 else ""
                log_level = parts[1] if len(parts) > 1 else "INFO"
                message = parts[2] if len(parts) > 2 else line
            else:
                timestamp = time.strftime("%Y-%m-%dT%H:%M:%S")
                log_level = "INFO"
                message = line
            
            # Filter by level if specified
            if level and log_level.upper() != level.upper():
                continue
            
            logs.append({
                "timestamp": timestamp,
                "level": log_level.upper(),
                "message": message,
            })
        
        return {"logs": logs[-lines:], "source": str(log_file)}
    except Exception as e:
        return {"logs": [], "error": str(e)}


# ── Ticket endpoints ─────────────────────────────────────────

@router.get("/tickets")
async def list_tickets(
    sprint_id: str | None = None,
    status: str | None = None,
    priority: str | None = None,
    tool: str | None = None,
    tag: str | None = None,
):
    """List tickets with optional filters."""
    mgr = _tickets()
    st = TicketStatus(status) if status else None
    pr = TicketPriority(priority) if priority else None
    tickets = mgr.list_tickets(sprint_id=sprint_id, status=st, priority=pr, tool=tool, tag=tag)
    return {"tickets": [t.summary() for t in tickets]}


@router.post("/tickets")
async def create_ticket(req: CreateTicketReq):
    """Create a new ticket."""
    ticket = Ticket(
        title=req.title,
        description=req.description,
        status=TicketStatus(req.status),
        priority=TicketPriority(req.priority),
        type=TicketType(req.type),
        sprint_id=req.sprint_id,
        tags=req.tags,
        files=req.files,
        estimated_hours=req.estimated_hours,
    )
    mgr = _tickets()
    created = mgr.create_ticket(ticket)
    return created.summary()


@router.get("/tickets/board/view")
async def ticket_board(sprint_id: str | None = None):
    """Kanban board view: tickets grouped by status columns."""
    return _tickets().board(sprint_id=sprint_id)


@router.get("/tickets/{ticket_id}")
async def get_ticket(ticket_id: str):
    """Get ticket details including comments."""
    ticket = _tickets().get_ticket(ticket_id)
    if not ticket:
        raise HTTPException(404, "Ticket not found")
    result = ticket.summary()
    result["comments"] = [c.model_dump() for c in ticket.comments]
    return result


@router.put("/tickets/{ticket_id}")
async def update_ticket(ticket_id: str, req: UpdateTicketReq):
    """Update a ticket's fields."""
    updates = {k: v for k, v in req.model_dump().items() if v is not None}
    ticket = _tickets().update_ticket(ticket_id, updates)
    if not ticket:
        raise HTTPException(404, "Ticket not found")
    return ticket.summary()


@router.delete("/tickets/{ticket_id}")
async def delete_ticket(ticket_id: str):
    """Delete a ticket."""
    if _tickets().delete_ticket(ticket_id):
        return {"ok": True}
    raise HTTPException(404, "Ticket not found")


@router.post("/tickets/{ticket_id}/assign")
async def assign_ticket_tool(ticket_id: str, req: AssignToolReq):
    """Assign a tool (vscode, aider, claude-code, etc.) to work on a ticket."""
    assignment = ToolAssignment(tool=req.tool, agent_mode=req.agent_mode, model=req.model)
    if _tickets().assign_tool(ticket_id, assignment):
        return {"ok": True, "ticket": _tickets().get_ticket(ticket_id).summary()}
    raise HTTPException(404, "Ticket not found")


@router.post("/tickets/{ticket_id}/comment")
async def add_ticket_comment(ticket_id: str, req: AddCommentReq):
    """Add a comment to a ticket (from any tool or user)."""
    comment = TicketComment(author=req.author, content=req.content)
    if _tickets().add_comment(ticket_id, comment):
        return {"ok": True}
    raise HTTPException(404, "Ticket not found")


# ── Sprint endpoints ─────────────────────────────────────────

@router.get("/sprints")
async def list_sprints(status: str | None = None):
    """List sprints."""
    st = SprintStatus(status) if status else None
    sprints = _tickets().list_sprints(status=st)
    mgr = _tickets()
    return {"sprints": [s.summary(mgr.sprint_tickets(s.id)) for s in sprints]}


@router.post("/sprints")
async def create_sprint(req: CreateSprintReq):
    """Create a new sprint."""
    sprint = Sprint(
        name=req.name,
        goal=req.goal,
        start_date=req.start_date,
        end_date=req.end_date,
    )
    created = _tickets().create_sprint(sprint)
    return created.summary()


@router.get("/sprints/{sprint_id}")
async def get_sprint(sprint_id: str):
    """Get sprint details with ticket stats."""
    mgr = _tickets()
    sprint = mgr.get_sprint(sprint_id)
    if not sprint:
        raise HTTPException(404, "Sprint not found")
    result = sprint.summary(mgr.sprint_tickets(sprint_id))
    result["stats"] = mgr.sprint_stats(sprint_id)
    return result


@router.put("/sprints/{sprint_id}")
async def update_sprint(sprint_id: str, req: UpdateSprintReq):
    """Update sprint fields."""
    updates = {k: v for k, v in req.model_dump().items() if v is not None}
    sprint = _tickets().update_sprint(sprint_id, updates)
    if not sprint:
        raise HTTPException(404, "Sprint not found")
    mgr = _tickets()
    return sprint.summary(mgr.sprint_tickets(sprint_id))


@router.delete("/sprints/{sprint_id}")
async def delete_sprint(sprint_id: str):
    """Delete a sprint."""
    if _tickets().delete_sprint(sprint_id):
        return {"ok": True}
    raise HTTPException(404, "Sprint not found")
