"""
Design System Server (DSS) - FastAPI Server

Portable API server providing:
- Project management (CRUD)
- Figma integration endpoints
- Discovery & health endpoints
- Activity tracking
- Runtime configuration management
- Service discovery (Storybook, etc.)

Modes:
- Server: Deployed remotely, serves design systems to teams
- Local: Dev companion, UI advisor, local services

Uses SQLite for persistence, integrates with Figma tools.
"""

import asyncio
import subprocess
import json
import os
from pathlib import Path
from typing import Optional, List, Dict, Any
from datetime import datetime

from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel

import sys
sys.path.insert(0, str(Path(__file__).parent.parent))

from config import config
from storage.json_store import (
    Projects, Components, SyncHistory, ActivityLog, Teams, Cache, get_stats
)
from figma.figma_tools import FigmaToolSuite


# === Runtime Configuration ===

class RuntimeConfig:
    """
    Runtime configuration that can be modified from the dashboard.
    Persists to .dss/runtime-config.json for portability.
    """
    def __init__(self):
        self.config_path = Path(__file__).parent.parent.parent / ".dss" / "runtime-config.json"
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
        self._data = self._load()

    def _load(self) -> dict:
        if self.config_path.exists():
            try:
                return json.loads(self.config_path.read_text())
            except:
                pass
        return {
            "mode": "local",  # "local" or "server"
            "figma": {"token": "", "configured": False},
            "services": {
                "storybook": {"enabled": False, "port": 6006, "url": ""},
                "chromatic": {"enabled": False, "project_token": ""},
                "github": {"enabled": False, "repo": ""},
            },
            "features": {
                "visual_qa": True,
                "token_sync": True,
                "code_gen": True,
                "ai_advisor": False,
            }
        }

    def _save(self):
        self.config_path.write_text(json.dumps(self._data, indent=2))

    def get(self, key: str = None):
        if key is None:
            # Return safe copy without secrets
            safe = self._data.copy()
            if safe.get("figma", {}).get("token"):
                safe["figma"]["token"] = "***configured***"
            return safe
        return self._data.get(key)

    def set(self, key: str, value: Any):
        self._data[key] = value
        self._save()
        return self._data[key]

    def update(self, updates: dict):
        for key, value in updates.items():
            if isinstance(value, dict) and isinstance(self._data.get(key), dict):
                self._data[key].update(value)
            else:
                self._data[key] = value
        self._save()
        return self.get()

    def set_figma_token(self, token: str):
        self._data["figma"]["token"] = token
        self._data["figma"]["configured"] = bool(token)
        self._save()
        # Also update the global config
        os.environ["FIGMA_TOKEN"] = token
        return {"configured": bool(token)}


runtime_config = RuntimeConfig()


# === Service Discovery ===

class ServiceDiscovery:
    """Discovers and manages companion services."""

    KNOWN_SERVICES = {
        "storybook": {"ports": [6006, 6007], "health": "/"},
        "chromatic": {"ports": [], "health": None},
        "vite": {"ports": [5173, 5174, 3000], "health": "/"},
        "webpack": {"ports": [8080, 8081], "health": "/"},
        "nextjs": {"ports": [3000, 3001], "health": "/"},
    }

    @classmethod
    async def discover(cls) -> dict:
        """Discover running services by checking known ports."""
        import socket

        discovered = {}
        for service, info in cls.KNOWN_SERVICES.items():
            for port in info["ports"]:
                try:
                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    sock.settimeout(0.5)
                    result = sock.connect_ex(('127.0.0.1', port))
                    sock.close()
                    if result == 0:
                        discovered[service] = {
                            "running": True,
                            "port": port,
                            "url": f"http://localhost:{port}"
                        }
                        break
                except:
                    pass
            if service not in discovered:
                discovered[service] = {"running": False, "port": None, "url": None}

        return discovered

    @classmethod
    async def check_storybook(cls) -> dict:
        """Check Storybook status specifically."""
        import httpx

        configured = runtime_config.get("services").get("storybook", {})
        port = configured.get("port", 6006)
        url = configured.get("url") or f"http://localhost:{port}"

        try:
            async with httpx.AsyncClient(timeout=2.0) as client:
                resp = await client.get(url)
                return {
                    "running": resp.status_code == 200,
                    "url": url,
                    "port": port
                }
        except:
            return {"running": False, "url": url, "port": port}


# === App Setup ===

app = FastAPI(
    title="Design System Server (DSS)",
    description="API for design system management and Figma integration",
    version="1.0.0"
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Mount Admin UI static files
UI_DIR = Path(__file__).parent.parent.parent / "admin-ui"
if UI_DIR.exists():
    app.mount("/admin-ui", StaticFiles(directory=str(UI_DIR), html=True), name="admin-ui")

# Initialize Figma tools
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))


# === Request/Response Models ===

class ProjectCreate(BaseModel):
    name: str
    description: str = ""
    figma_file_key: str = ""

class ProjectUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    figma_file_key: Optional[str] = None
    status: Optional[str] = None

class FigmaExtractRequest(BaseModel):
    file_key: str
    format: str = "css"

class FigmaSyncRequest(BaseModel):
    file_key: str
    target_path: str
    format: str = "css"

class TeamCreate(BaseModel):
    name: str
    description: str = ""


# === Root & Health ===

@app.get("/")
async def root():
    """Redirect to Admin UI dashboard."""
    from fastapi.responses import RedirectResponse
    return RedirectResponse(url="/admin-ui/index.html")


@app.get("/health")
async def health():
    """Health check endpoint."""
    return {
        "status": "ok",
        "name": "dss-api",
        "version": "1.0.0",
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "figma_mode": figma_suite.mode,
        "config": config.summary()
    }

@app.get("/api/stats")
async def get_statistics():
    """Get database and system statistics."""
    db_stats = get_stats()
    return {
        "database": db_stats,
        "figma": {
            "mode": figma_suite.mode,
            "configured": config.figma.is_configured
        }
    }


# === Projects ===

@app.get("/api/projects")
async def list_projects(status: Optional[str] = None):
    """List all projects."""
    projects = Projects.list(status=status)
    return projects

@app.get("/api/projects/{project_id}")
async def get_project(project_id: str):
    """Get a specific project."""
    project = Projects.get(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    return project

@app.post("/api/projects")
async def create_project(project: ProjectCreate):
    """Create a new project."""
    project_id = f"proj-{int(datetime.utcnow().timestamp() * 1000)}"
    created = Projects.create(
        id=project_id,
        name=project.name,
        description=project.description,
        figma_file_key=project.figma_file_key
    )
    ActivityLog.log(
        action="project_created",
        entity_type="project",
        entity_id=project_id,
        project_id=project_id,
        details={"name": project.name}
    )
    return created

@app.put("/api/projects/{project_id}")
async def update_project(project_id: str, update: ProjectUpdate):
    """Update a project."""
    existing = Projects.get(project_id)
    if not existing:
        raise HTTPException(status_code=404, detail="Project not found")

    update_data = {k: v for k, v in update.dict().items() if v is not None}
    if not update_data:
        return existing

    updated = Projects.update(project_id, **update_data)
    ActivityLog.log(
        action="project_updated",
        entity_type="project",
        entity_id=project_id,
        project_id=project_id,
        details=update_data
    )
    return updated

@app.delete("/api/projects/{project_id}")
async def delete_project(project_id: str):
    """Delete a project."""
    if not Projects.delete(project_id):
        raise HTTPException(status_code=404, detail="Project not found")
    ActivityLog.log(
        action="project_deleted",
        entity_type="project",
        entity_id=project_id
    )
    return {"success": True}


# === Components ===

@app.get("/api/projects/{project_id}/components")
async def list_components(project_id: str):
    """List components for a project."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")
    return Components.list(project_id)


# === Figma Integration ===

@app.post("/api/figma/extract-variables")
async def extract_variables(request: FigmaExtractRequest, background_tasks: BackgroundTasks):
    """Extract design tokens from Figma file."""
    try:
        result = await figma_suite.extract_variables(request.file_key, request.format)
        ActivityLog.log(
            action="figma_extract_variables",
            entity_type="figma",
            details={"file_key": request.file_key, "format": request.format, "count": result.get("tokens_count")}
        )
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/figma/extract-components")
async def extract_components(request: FigmaExtractRequest):
    """Extract components from Figma file."""
    try:
        result = await figma_suite.extract_components(request.file_key)
        ActivityLog.log(
            action="figma_extract_components",
            entity_type="figma",
            details={"file_key": request.file_key, "count": result.get("components_count")}
        )
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/figma/extract-styles")
async def extract_styles(request: FigmaExtractRequest):
    """Extract styles from Figma file."""
    try:
        result = await figma_suite.extract_styles(request.file_key)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/figma/sync-tokens")
async def sync_tokens(request: FigmaSyncRequest):
    """Sync tokens from Figma to target path."""
    try:
        result = await figma_suite.sync_tokens(request.file_key, request.target_path, request.format)
        ActivityLog.log(
            action="figma_sync_tokens",
            entity_type="figma",
            details={"file_key": request.file_key, "target": request.target_path, "synced": result.get("tokens_synced")}
        )
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/figma/validate")
async def validate_components(request: FigmaExtractRequest):
    """Validate components against design system rules."""
    try:
        result = await figma_suite.validate_components(request.file_key)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/figma/generate-code")
async def generate_code(file_key: str, component_name: str, framework: str = "webcomponent"):
    """Generate component code from Figma."""
    try:
        result = await figma_suite.generate_code(file_key, component_name, framework)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# === Discovery ===

@app.get("/api/discovery")
async def run_discovery(path: str = "."):
    """Run project discovery."""
    script_path = Path(__file__).parent.parent / "discovery" / "discover.sh"

    try:
        result = subprocess.run(
            [str(script_path), path],
            capture_output=True,
            text=True,
            timeout=30
        )
        if result.returncode == 0:
            return json.loads(result.stdout)
        else:
            return {"error": result.stderr}
    except subprocess.TimeoutExpired:
        raise HTTPException(status_code=504, detail="Discovery timed out")
    except json.JSONDecodeError:
        return {"raw_output": result.stdout}

@app.get("/api/discovery/ports")
async def discover_ports():
    """Discover listening ports and services."""
    script_path = Path(__file__).parent.parent / "discovery" / "discover-ports.sh"

    try:
        result = subprocess.run(
            [str(script_path)],
            capture_output=True,
            text=True,
            timeout=10
        )
        return json.loads(result.stdout)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/discovery/env")
async def discover_env(path: str = "."):
    """Analyze environment configuration."""
    script_path = Path(__file__).parent.parent / "discovery" / "discover-env.sh"

    try:
        result = subprocess.run(
            [str(script_path), path],
            capture_output=True,
            text=True,
            timeout=10
        )
        return json.loads(result.stdout)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# === Activity & Sync History ===

@app.get("/api/activity")
async def get_activity(limit: int = Query(default=50, le=100)):
    """Get recent activity log."""
    return ActivityLog.recent(limit=limit)

@app.get("/api/sync-history")
async def get_sync_history(project_id: Optional[str] = None, limit: int = Query(default=20, le=100)):
    """Get sync history."""
    return SyncHistory.recent(project_id=project_id, limit=limit)


# === Teams ===

@app.get("/api/teams")
async def list_teams():
    """List all teams."""
    return Teams.list()

@app.post("/api/teams")
async def create_team(team: TeamCreate):
    """Create a new team."""
    team_id = f"team-{int(datetime.utcnow().timestamp() * 1000)}"
    created = Teams.create(team_id, team.name, team.description)
    return created

@app.get("/api/teams/{team_id}")
async def get_team(team_id: str):
    """Get a specific team."""
    team = Teams.get(team_id)
    if not team:
        raise HTTPException(status_code=404, detail="Team not found")
    return team


# === Cache Management ===

@app.post("/api/cache/clear")
async def clear_cache():
    """Clear expired cache entries."""
    count = Cache.clear_expired()
    return {"cleared": count}

@app.delete("/api/cache")
async def purge_cache():
    """Purge all cache entries."""
    Cache.clear_all()
    return {"success": True}


# === Configuration Management ===

class ConfigUpdate(BaseModel):
    mode: Optional[str] = None
    figma_token: Optional[str] = None
    services: Optional[Dict[str, Any]] = None
    features: Optional[Dict[str, bool]] = None


@app.get("/api/config")
async def get_config():
    """Get current runtime configuration (secrets masked)."""
    return {
        "config": runtime_config.get(),
        "env": config.summary(),
        "mode": runtime_config.get("mode")
    }


@app.put("/api/config")
async def update_config(update: ConfigUpdate):
    """Update runtime configuration."""
    updates = {}

    if update.mode:
        updates["mode"] = update.mode

    if update.figma_token is not None:
        runtime_config.set_figma_token(update.figma_token)
        # Reinitialize Figma tools with new token
        global figma_suite
        figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
        ActivityLog.log(
            action="figma_token_updated",
            entity_type="config",
            details={"configured": bool(update.figma_token)}
        )

    if update.services:
        updates["services"] = update.services

    if update.features:
        updates["features"] = update.features

    if updates:
        runtime_config.update(updates)
        ActivityLog.log(
            action="config_updated",
            entity_type="config",
            details={"keys": list(updates.keys())}
        )

    return runtime_config.get()


@app.get("/api/config/figma")
async def get_figma_config():
    """Get Figma configuration status."""
    figma_cfg = runtime_config.get("figma")
    return {
        "configured": figma_cfg.get("configured", False),
        "mode": figma_suite.mode,
        "features": {
            "extract_variables": True,
            "extract_components": True,
            "extract_styles": True,
            "sync_tokens": True,
            "validate": True,
            "generate_code": True,
        }
    }


@app.post("/api/config/figma/test")
async def test_figma_connection():
    """Test Figma API connection."""
    try:
        # Try to make a simple API call
        if not runtime_config.get("figma").get("configured"):
            return {"success": False, "error": "Figma token not configured"}

        # Test with a minimal API call
        import httpx
        token = runtime_config._data["figma"]["token"]
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                "https://api.figma.com/v1/me",
                headers={"X-Figma-Token": token}
            )
            if resp.status_code == 200:
                user = resp.json()
                return {
                    "success": True,
                    "user": user.get("email", "connected"),
                    "handle": user.get("handle")
                }
            else:
                return {"success": False, "error": f"API returned {resp.status_code}"}
    except Exception as e:
        return {"success": False, "error": str(e)}


# === Service Discovery ===

@app.get("/api/services")
async def list_services():
    """List configured and discovered services."""
    configured = runtime_config.get("services")
    discovered = await ServiceDiscovery.discover()

    return {
        "configured": configured,
        "discovered": discovered,
        "storybook": await ServiceDiscovery.check_storybook()
    }


@app.put("/api/services/{service_name}")
async def configure_service(service_name: str, config_data: Dict[str, Any]):
    """Configure a service."""
    services = runtime_config.get("services") or {}
    services[service_name] = {**services.get(service_name, {}), **config_data}
    runtime_config.set("services", services)

    ActivityLog.log(
        action="service_configured",
        entity_type="service",
        entity_id=service_name,
        details={"keys": list(config_data.keys())}
    )

    return services[service_name]


@app.get("/api/services/storybook")
async def get_storybook_status():
    """Get Storybook service status."""
    return await ServiceDiscovery.check_storybook()


# === DSS Mode ===

@app.get("/api/mode")
async def get_mode():
    """Get current DSS mode."""
    mode = runtime_config.get("mode")
    return {
        "mode": mode,
        "description": "Local dev companion" if mode == "local" else "Remote design system server",
        "features": runtime_config.get("features")
    }


@app.put("/api/mode")
async def set_mode(mode: str):
    """Set DSS mode (local or server)."""
    if mode not in ["local", "server"]:
        raise HTTPException(status_code=400, detail="Mode must be 'local' or 'server'")

    runtime_config.set("mode", mode)
    ActivityLog.log(
        action="mode_changed",
        entity_type="config",
        details={"mode": mode}
    )

    return {"mode": mode, "success": True}


# === Run Server ===

# === Static Files (Admin UI) ===
# Mount at the end so API routes take precedence
# This enables portable mode: ./dss start serves everything on one port

UI_DIR = Path(__file__).parent.parent.parent / "admin-ui"
if UI_DIR.exists():
    app.mount("/", StaticFiles(directory=str(UI_DIR), html=True), name="ui")


if __name__ == "__main__":
    import uvicorn

    port = int(os.getenv("PORT", "3456"))
    host = os.getenv("HOST", "0.0.0.0")

    url = f"http://{host}:{port}"
    print(f"""
╔═══════════════════════════════════════════════════════════════╗
║  Design System Server (DSS) - Portable Server                 ║
╠═══════════════════════════════════════════════════════════════╣
║  Dashboard:   {url + '/':^47}║
║  API:         {url + '/api':^47}║
║  Docs:        {url + '/docs':^47}║
║  Environment: {config.server.env:^47}║
║  Figma Mode:  {figma_suite.mode:^47}║
╚═══════════════════════════════════════════════════════════════╝
""")

    uvicorn.run(
        "server:app",
        host=host,
        port=port,
        reload=config.server.env == "development"
    )
