"""
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, Depends, Header
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Optional

import sys
# Add tools directory to path (legacy imports)
sys.path.insert(0, str(Path(__file__).parent.parent))
# Add dss-mvp1 directory to path (consolidated dss package)
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "dss-mvp1"))

# Legacy imports (will gradually migrate these)
from config import config
from storage.database import (
    Projects, Components, SyncHistory, ActivityLog, Teams, Cache, get_stats,
    FigmaFiles, ESREDefinitions, TokenDriftDetector, CodeMetrics, TestResults
)
from figma.figma_tools import FigmaToolSuite

# New consolidated dss imports - now available!
# from dss import DesignToken, TokenSource, ProjectScanner, etc.
# from dss.ingest import CSSTokenSource, SCSSTokenSource, TailwindTokenSource
# from dss.analyze import ReactAnalyzer, StyleAnalyzer, QuickWinFinder
# from dss.storybook import StorybookScanner, StoryGenerator


# === 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 (json.JSONDecodeError, IOError) as e:
                # Config file corrupted or unreadable, use defaults
                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 (OSError, socket.error):
                    # Service not running on this port
                    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 (httpx.ConnectError, httpx.TimeoutException, httpx.HTTPError):
            # Storybook not running or unreachable
            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 with token from runtime config
figma_config = runtime_config.get("figma")
figma_token_at_startup = figma_config.get("token") if figma_config else None
figma_suite = FigmaToolSuite(
    token=figma_token_at_startup,
    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 = ""

class FigmaFileCreate(BaseModel):
    figma_url: str
    file_name: str
    file_key: str

class ESRECreate(BaseModel):
    name: str
    definition_text: str
    expected_value: Optional[str] = None
    component_name: Optional[str] = None

class TokenDriftCreate(BaseModel):
    component_id: str
    property_name: str
    hardcoded_value: str
    file_path: str
    line_number: int
    severity: str = "warning"
    suggested_token: Optional[str] = None


# === Authentication ===

from auth.atlassian_auth import get_auth

async def get_current_user(authorization: Optional[str] = Header(None)) -> Dict[str, Any]:
    """
    Dependency to get current authenticated user from JWT token.
    Usage: user = Depends(get_current_user)
    """
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Not authenticated")

    token = authorization.replace("Bearer ", "")
    auth = get_auth()
    user_data = auth.verify_token(token)

    if not user_data:
        raise HTTPException(status_code=401, detail="Invalid or expired token")

    return user_data

class LoginRequest(BaseModel):
    url: str  # Atlassian URL
    email: str
    api_token: str
    service: str = "jira"  # "jira" or "confluence"

@app.post("/api/auth/login")
async def login(request: LoginRequest):
    """
    Authenticate with Atlassian credentials.

    Validates credentials against Jira or Confluence API,
    creates/updates user in database, returns JWT token.
    """
    try:
        auth = get_auth()
        result = await auth.login(
            url=request.url,
            email=request.email,
            api_token=request.api_token,
            service=request.service
        )
        return result
    except ValueError as e:
        raise HTTPException(status_code=401, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Login failed: {str(e)}")

@app.get("/api/auth/me")
async def get_me(user: Dict[str, Any] = Depends(get_current_user)):
    """Get current authenticated user info"""
    auth = get_auth()
    user_data = await auth.get_user_by_id(user["user_id"])
    if not user_data:
        raise HTTPException(status_code=404, detail="User not found")
    return user_data

# === 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 for monitoring.
    Returns 200 OK if service is healthy.
    """
    import os
    import psutil
    from pathlib import Path

    # Check database connectivity
    db_ok = False
    try:
        with get_connection() as conn:
            conn.execute("SELECT 1").fetchone()
            db_ok = True
    except:
        pass

    # Check MCP handler
    mcp_ok = False
    try:
        from dss_mcp.handler import get_mcp_handler
        handler = get_mcp_handler()
        mcp_ok = handler is not None
    except:
        pass

    # Get uptime from process
    try:
        process = psutil.Process(os.getpid())
        uptime_seconds = int((datetime.now() - datetime.fromtimestamp(process.create_time())).total_seconds())
    except:
        uptime_seconds = 0

    # Overall status
    status = "healthy" if (db_ok and mcp_ok) else "degraded"

    return {
        "status": status,
        "version": "0.8.0",
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "uptime_seconds": uptime_seconds,
        "components": {
            "database": "ok" if db_ok else "error",
            "mcp": "ok" if mcp_ok else "error",
            "figma": "ok" if config.figma.is_configured else "not_configured"
        }
    }

@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))

@app.get("/api/figma/health")
async def figma_health():
    """Health check for Figma integration."""
    is_live = figma_suite.mode == 'live'
    return {
        "status": "ok" if is_live else "degraded",
        "mode": figma_suite.mode,
        "message": "Figma is connected." if is_live else "Figma is running in mock mode. Please configure a token."
    }


# === 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}

class DiscoveryScanRequest(BaseModel):
    path: str = "."
    full_scan: bool = False

@app.post("/api/discovery/scan")
async def scan_project(request: DiscoveryScanRequest):
    """Run project discovery scan."""
    script_path = Path(__file__).parent.parent / "discovery" / "discover.sh"

    try:
        result = subprocess.run(
            [str(script_path), request.path],
            capture_output=True,
            text=True,
            timeout=30
        )
        if result.returncode == 0:
            data = json.loads(result.stdout)
            ActivityLog.log(
                action="discovery_scan",
                entity_type="project",
                details={"path": request.path, "full_scan": request.full_scan}
            )
            return data
        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/stats")
async def get_discovery_stats():
    """Get project statistics."""
    db_stats = get_stats()
    return {
        "projects": db_stats.get("projects", {}),
        "tokens": db_stats.get("tokens", {"total": 0}),
        "components": db_stats.get("components", {"total": 0}),
        "syncs": {
            "today": 0,
            "this_week": 0,
            "total": db_stats.get("syncs", {}).get("total", 0),
            "last_sync": None
        },
        "stories": {
            "total": 0
        }
    }

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

@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)


# === Audit Log (Enhanced) ===

@app.get("/api/audit")
async def get_audit_log(
    project_id: Optional[str] = None,
    user_id: Optional[str] = None,
    action: Optional[str] = None,
    category: Optional[str] = None,
    entity_type: Optional[str] = None,
    severity: Optional[str] = None,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
    limit: int = Query(default=50, le=200),
    offset: int = Query(default=0, ge=0)
):
    """
    Get audit log with advanced filtering.

    Query parameters:
    - project_id: Filter by project
    - user_id: Filter by user
    - action: Filter by specific action
    - category: Filter by category (design_system, code, configuration, etc.)
    - entity_type: Filter by entity type (project, component, token, etc.)
    - severity: Filter by severity (info, warning, critical)
    - start_date: Filter from date (ISO format)
    - end_date: Filter to date (ISO format)
    - limit: Number of results (max 200)
    - offset: Pagination offset
    """
    activities = ActivityLog.search(
        project_id=project_id,
        user_id=user_id,
        action=action,
        category=category,
        entity_type=entity_type,
        severity=severity,
        start_date=start_date,
        end_date=end_date,
        limit=limit,
        offset=offset
    )

    total = ActivityLog.count(
        project_id=project_id,
        user_id=user_id,
        action=action,
        category=category
    )

    return {
        "activities": activities,
        "total": total,
        "limit": limit,
        "offset": offset,
        "has_more": (offset + limit) < total
    }

@app.get("/api/audit/stats")
async def get_audit_stats():
    """Get audit log statistics."""
    return {
        "by_category": ActivityLog.get_stats_by_category(),
        "by_user": ActivityLog.get_stats_by_user(),
        "total_count": ActivityLog.count()
    }

@app.get("/api/audit/categories")
async def get_audit_categories():
    """Get list of all activity categories."""
    return ActivityLog.get_categories()

@app.get("/api/audit/actions")
async def get_audit_actions():
    """Get list of all activity actions."""
    return ActivityLog.get_actions()

class AuditLogRequest(BaseModel):
    action: str
    entity_type: Optional[str] = None
    entity_id: Optional[str] = None
    entity_name: Optional[str] = None
    project_id: Optional[str] = None
    user_id: Optional[str] = None
    user_name: Optional[str] = None
    team_context: Optional[str] = None
    description: Optional[str] = None
    category: Optional[str] = None
    severity: str = 'info'
    details: Optional[Dict[str, Any]] = None

@app.post("/api/audit")
async def create_audit_entry(entry: AuditLogRequest, request: Any):
    """
    Create a new audit log entry.
    Automatically captures IP and user agent from request.
    """
    # Extract IP and user agent from request
    ip_address = request.client.host if hasattr(request, 'client') else None
    user_agent = request.headers.get('user-agent') if hasattr(request, 'headers') else None

    ActivityLog.log(
        action=entry.action,
        entity_type=entry.entity_type,
        entity_id=entry.entity_id,
        entity_name=entry.entity_name,
        project_id=entry.project_id,
        user_id=entry.user_id,
        user_name=entry.user_name,
        team_context=entry.team_context,
        description=entry.description,
        category=entry.category,
        severity=entry.severity,
        details=entry.details,
        ip_address=ip_address,
        user_agent=user_agent
    )

    return {"success": True, "message": "Audit entry created"}

@app.get("/api/audit/export")
async def export_audit_log(
    project_id: Optional[str] = None,
    category: Optional[str] = None,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
    format: str = Query(default="json", regex="^(json|csv)$")
):
    """
    Export audit log in JSON or CSV format.
    """
    activities = ActivityLog.search(
        project_id=project_id,
        category=category,
        start_date=start_date,
        end_date=end_date,
        limit=10000  # Max export limit
    )

    if format == "csv":
        import csv
        import io
        from fastapi.responses import StreamingResponse

        output = io.StringIO()
        if activities:
            fieldnames = ['created_at', 'user_name', 'action', 'category', 'description', 'project_id', 'entity_type', 'entity_name', 'severity']
            writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore')
            writer.writeheader()
            writer.writerows(activities)

        output.seek(0)
        return StreamingResponse(
            iter([output.getvalue()]),
            media_type="text/csv",
            headers={"Content-Disposition": f"attachment; filename=audit_log_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.csv"}
        )
    else:
        # JSON format
        return {
            "activities": activities,
            "total": len(activities),
            "exported_at": datetime.utcnow().isoformat() + "Z"
        }


# === 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(
            token=update.figma_token,
            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

# === System Administration ===

@app.post("/api/system/reset")
async def reset_dss(request_data: Dict[str, Any]):
    """
    Reset DSS to fresh state by calling the reset command in dss-mvp1.
    Requires confirmation.
    """
    confirm = request_data.get("confirm", "")

    if confirm != "RESET":
        raise HTTPException(status_code=400, detail="Must confirm with 'RESET'")

    try:
        # Path to dss-mvp1 directory
        dss_mvp1_path = Path(__file__).parent.parent.parent / "dss-mvp1"

        # Run the reset command
        result = subprocess.run(
            ["python3", "-m", "dss.settings", "reset", "--no-confirm"],
            cwd=str(dss_mvp1_path),
            capture_output=True,
            text=True,
            timeout=60
        )

        if result.returncode != 0:
            raise Exception(f"Reset failed: {result.stderr}")

        ActivityLog.log(
            action="dss_reset",
            entity_type="system",
            details={"status": "success"}
        )

        return {
            "success": True,
            "message": "DSS has been reset to fresh state",
            "output": result.stdout
        }

    except subprocess.TimeoutExpired:
        raise HTTPException(status_code=504, detail="Reset operation timed out")
    except Exception as e:
        ActivityLog.log(
            action="dss_reset_failed",
            entity_type="system",
            details={"error": str(e)}
        )
        raise HTTPException(status_code=500, detail=str(e))


# === Team Dashboards ===

@app.get("/api/projects/{project_id}/dashboard/summary")
async def get_dashboard_summary(project_id: str):
    """
    Get dashboard summary for all teams (thin slice).
    Provides overview of UX, UI, and QA metrics.
    """
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    # UX Dashboard data
    figma_files = FigmaFiles.list(project_id)

    # UI Dashboard data
    drift_stats = TokenDriftDetector.get_stats(project_id)
    code_summary = CodeMetrics.get_project_summary(project_id)

    # QA Dashboard data
    esre_list = ESREDefinitions.list(project_id)
    test_summary = TestResults.get_project_summary(project_id)

    return {
        "project_id": project_id,
        "ux": {
            "figma_files_count": len(figma_files),
            "figma_files": figma_files[:5]  # Show first 5
        },
        "ui": {
            "token_drift": drift_stats,
            "code_metrics": code_summary
        },
        "qa": {
            "esre_count": len(esre_list),
            "test_summary": test_summary
        }
    }


# === UX Dashboard: Figma File Management ===

@app.get("/api/projects/{project_id}/figma-files")
async def list_figma_files(project_id: str):
    """List all Figma files for a project (UX Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")
    return FigmaFiles.list(project_id)


@app.post("/api/projects/{project_id}/figma-files")
async def create_figma_file(project_id: str, figma_file: FigmaFileCreate):
    """Add a Figma file to a project (UX Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    created = FigmaFiles.create(
        project_id=project_id,
        figma_url=figma_file.figma_url,
        file_name=figma_file.file_name,
        file_key=figma_file.file_key
    )

    ActivityLog.log(
        action="figma_file_added",
        entity_type="figma_file",
        entity_id=str(created['id']),
        entity_name=figma_file.file_name,
        project_id=project_id,
        team_context="ux",
        details={"file_key": figma_file.file_key}
    )

    return created


@app.put("/api/projects/{project_id}/figma-files/{file_id}/sync")
async def update_figma_file_sync(project_id: str, file_id: int, status: str = "synced"):
    """Update Figma file sync status (UX Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    updated = FigmaFiles.update_sync_status(
        file_id=file_id,
        status=status,
        last_synced=datetime.utcnow().isoformat()
    )

    if not updated:
        raise HTTPException(status_code=404, detail="Figma file not found")

    ActivityLog.log(
        action="figma_file_synced",
        entity_type="figma_file",
        entity_id=str(file_id),
        project_id=project_id,
        team_context="ux"
    )

    return updated


@app.delete("/api/projects/{project_id}/figma-files/{file_id}")
async def delete_figma_file(project_id: str, file_id: int):
    """Delete a Figma file (UX Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    if not FigmaFiles.delete(file_id):
        raise HTTPException(status_code=404, detail="Figma file not found")

    ActivityLog.log(
        action="figma_file_deleted",
        entity_type="figma_file",
        entity_id=str(file_id),
        project_id=project_id,
        team_context="ux"
    )

    return {"success": True}


# === UI Dashboard: Token Drift Detection ===

@app.get("/api/projects/{project_id}/token-drift")
async def list_token_drift(project_id: str, severity: Optional[str] = None):
    """List token drift issues for a project (UI Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    drifts = TokenDriftDetector.list_by_project(project_id, severity)
    stats = TokenDriftDetector.get_stats(project_id)

    return {
        "drifts": drifts,
        "stats": stats
    }


@app.post("/api/projects/{project_id}/token-drift")
async def record_token_drift(project_id: str, drift: TokenDriftCreate):
    """Record a token drift issue (UI Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    created = TokenDriftDetector.record_drift(
        component_id=drift.component_id,
        property_name=drift.property_name,
        hardcoded_value=drift.hardcoded_value,
        file_path=drift.file_path,
        line_number=drift.line_number,
        severity=drift.severity,
        suggested_token=drift.suggested_token
    )

    ActivityLog.log(
        action="token_drift_detected",
        entity_type="token_drift",
        entity_id=str(created['id']),
        project_id=project_id,
        team_context="ui",
        details={
            "severity": drift.severity,
            "component_id": drift.component_id
        }
    )

    return created


@app.put("/api/projects/{project_id}/token-drift/{drift_id}/status")
async def update_drift_status(project_id: str, drift_id: int, status: str):
    """Update token drift status: pending, fixed, ignored (UI Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    if status not in ["pending", "fixed", "ignored"]:
        raise HTTPException(status_code=400, detail="Invalid status")

    updated = TokenDriftDetector.update_status(drift_id, status)

    if not updated:
        raise HTTPException(status_code=404, detail="Drift issue not found")

    ActivityLog.log(
        action="token_drift_status_updated",
        entity_type="token_drift",
        entity_id=str(drift_id),
        project_id=project_id,
        team_context="ui",
        details={"status": status}
    )

    return updated


# === QA Dashboard: ESRE Definitions ===

@app.get("/api/projects/{project_id}/esre")
async def list_esre_definitions(project_id: str):
    """List all ESRE definitions for a project (QA Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")
    return ESREDefinitions.list(project_id)


@app.post("/api/projects/{project_id}/esre")
async def create_esre_definition(project_id: str, esre: ESRECreate):
    """Create a new ESRE definition (QA Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    created = ESREDefinitions.create(
        project_id=project_id,
        name=esre.name,
        definition_text=esre.definition_text,
        expected_value=esre.expected_value,
        component_name=esre.component_name
    )

    ActivityLog.log(
        action="esre_created",
        entity_type="esre",
        entity_id=str(created['id']),
        entity_name=esre.name,
        project_id=project_id,
        team_context="qa"
    )

    return created


@app.put("/api/projects/{project_id}/esre/{esre_id}")
async def update_esre_definition(project_id: str, esre_id: int, updates: ESRECreate):
    """Update an ESRE definition (QA Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    updated = ESREDefinitions.update(
        esre_id=esre_id,
        name=updates.name,
        definition_text=updates.definition_text,
        expected_value=updates.expected_value,
        component_name=updates.component_name
    )

    if not updated:
        raise HTTPException(status_code=404, detail="ESRE definition not found")

    ActivityLog.log(
        action="esre_updated",
        entity_type="esre",
        entity_id=str(esre_id),
        entity_name=updates.name,
        project_id=project_id,
        team_context="qa"
    )

    return updated


@app.delete("/api/projects/{project_id}/esre/{esre_id}")
async def delete_esre_definition(project_id: str, esre_id: int):
    """Delete an ESRE definition (QA Dashboard)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    if not ESREDefinitions.delete(esre_id):
        raise HTTPException(status_code=404, detail="ESRE definition not found")

    ActivityLog.log(
        action="esre_deleted",
        entity_type="esre",
        entity_id=str(esre_id),
        project_id=project_id,
        team_context="qa"
    )

    return {"success": True}


# === Claude Chat API with MCP Tool Integration ===

class ClaudeChatRequest(BaseModel):
    """Claude chat request model"""
    message: str
    context: Optional[Dict[str, Any]] = {}
    history: Optional[List[Dict[str, Any]]] = []
    project_id: Optional[str] = None
    user_id: Optional[int] = 1
    enable_tools: Optional[bool] = True


@app.post("/api/claude/chat")
async def claude_chat(request_data: ClaudeChatRequest):
    """
    Chat with Claude AI via Anthropic API with MCP tool integration.

    Claude can now execute DSS tools to:
    - Get project information
    - List/search components
    - Get design tokens
    - Interact with Figma, Jira, Confluence

    Requires ANTHROPIC_API_KEY environment variable.
    """
    message = request_data.message
    context = request_data.context or {}
    history = request_data.history or []
    project_id = request_data.project_id or context.get("projectId")
    user_id = request_data.user_id or 1
    enable_tools = request_data.enable_tools

    # Log the chat request
    ActivityLog.log(
        action="claude_chat",
        entity_type="chat",
        entity_id="claude",
        details={"message_length": len(message), "tools_enabled": enable_tools}
    )

    try:
        # Try to import Anthropic SDK
        try:
            from anthropic import Anthropic
        except ImportError:
            return {
                "success": False,
                "response": "Anthropic SDK not installed. Install it with: pip install anthropic",
                "model": "error"
            }

        # Check for API key
        api_key = os.getenv("ANTHROPIC_API_KEY")
        if not api_key:
            return {
                "success": False,
                "response": "Claude API key not configured. Set ANTHROPIC_API_KEY environment variable or add to .env file.",
                "model": "error"
            }

        # Import MCP handler
        from dss_mcp.handler import get_mcp_handler, MCPContext

        mcp_handler = get_mcp_handler()

        # Build system prompt with design system context
        system_prompt = """You are a design system assistant with access to DSS (Design System Server) tools.

You can use tools to:
- Get project summaries, health scores, and statistics
- List and search components in the design system
- Get design tokens (colors, typography, spacing)
- Interact with Figma to extract designs
- Create/search Jira issues for tracking
- Access Confluence documentation

RULES:
- Use tools when the user asks about project data, components, or tokens
- Be concise: 2-3 sentences for simple questions
- When showing tool results, summarize key information
- If a tool fails, explain what went wrong
- Always provide actionable insights from tool data"""

        # Add project context if available
        if project_id:
            try:
                project_context = await mcp_handler.get_project_context(project_id, user_id)
                if project_context:
                    system_prompt += f"""

CURRENT PROJECT CONTEXT:
- Project: {project_context.name} (ID: {project_id})
- Components: {project_context.component_count}
- Health Score: {project_context.health.get('score', 'N/A')}/100 (Grade: {project_context.health.get('grade', 'N/A')})
- Integrations: {', '.join(project_context.integrations.keys()) if project_context.integrations else 'None configured'}"""
            except:
                system_prompt += f"\n\nProject ID: {project_id} (context not loaded)"

        # Add user context
        if context:
            context_parts = []
            if "project" in context:
                context_parts.append(f"Project: {context['project']}")
            if "file" in context:
                context_parts.append(f"Current file: {context['file']}")
            if "component" in context:
                context_parts.append(f"Component: {context['component']}")
            if context_parts:
                system_prompt += f"\n\nUser context:\n" + "\n".join(context_parts)

        # Build conversation messages
        messages = []

        # Add recent history (handle tool_use in history)
        for msg in history[-6:]:
            role = msg.get("role", "user")
            content = msg.get("content", "")
            if content and role in ["user", "assistant"]:
                messages.append({
                    "role": role,
                    "content": content
                })

        # Add current message
        messages.append({
            "role": "user",
            "content": message
        })

        # Get tools for Claude if enabled
        tools = None
        if enable_tools and project_id:
            tools = mcp_handler.get_tools_for_claude()

        # Call Claude API
        client = Anthropic(api_key=api_key)

        # Initial API call
        api_params = {
            "model": "claude-sonnet-4-5-20250929",
            "max_tokens": 4096,
            "temperature": 0.7,
            "system": system_prompt,
            "messages": messages
        }

        if tools:
            api_params["tools"] = tools

        response = await asyncio.to_thread(
            client.messages.create,
            **api_params
        )

        # Handle tool use loop
        tools_used = []
        max_iterations = 5
        iteration = 0

        while response.stop_reason == "tool_use" and iteration < max_iterations:
            iteration += 1

            # Process tool calls
            tool_results = []
            for content_block in response.content:
                if content_block.type == "tool_use":
                    tool_name = content_block.name
                    tool_input = content_block.input
                    tool_use_id = content_block.id

                    # Execute the tool
                    mcp_context = MCPContext(
                        project_id=project_id,
                        user_id=user_id
                    )

                    result = await mcp_handler.execute_tool(
                        tool_name=tool_name,
                        arguments=tool_input,
                        context=mcp_context
                    )

                    tools_used.append({
                        "tool": tool_name,
                        "success": result.success,
                        "duration_ms": result.duration_ms
                    })

                    # Format result for Claude
                    if result.success:
                        tool_result_content = json.dumps(result.result, indent=2)
                    else:
                        tool_result_content = json.dumps({"error": result.error})

                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": tool_result_content
                    })

            # Add assistant response and tool results to messages
            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": tool_results})

            # Call Claude again with tool results
            response = await asyncio.to_thread(
                client.messages.create,
                **{**api_params, "messages": messages}
            )

        # Extract final response text
        response_text = ""
        for content_block in response.content:
            if hasattr(content_block, "text"):
                response_text += content_block.text

        # Log tool usage
        if tools_used:
            ActivityLog.log(
                action="claude_tools_used",
                entity_type="chat",
                entity_id="claude",
                project_id=project_id,
                details={"tools": tools_used}
            )

        return {
            "success": True,
            "response": response_text,
            "model": response.model,
            "tools_used": tools_used,
            "stop_reason": response.stop_reason
        }

    except Exception as e:
        error_msg = str(e)
        return {
            "success": False,
            "response": f"Error connecting to Claude: {error_msg}\n\nMake sure your ANTHROPIC_API_KEY is valid and you have API access.",
            "model": "error"
        }


# === MCP Tools Proxy ===

@app.post("/api/mcp/{tool_name}")
async def execute_mcp_tool(tool_name: str, params: Dict[str, Any] = {}):
    """
    Proxy MCP tool execution.
    Calls the MCP server running on port 3457.
    """
    try:
        # Import MCP server functions
        from mcp_server import (
            get_status, list_projects, create_project, get_project,
            extract_tokens, extract_components, generate_component_code,
            sync_tokens_to_file, get_sync_history, get_activity,
            ingest_css_tokens, ingest_scss_tokens, ingest_tailwind_tokens,
            ingest_json_tokens, merge_tokens, export_tokens, validate_tokens,
            discover_project, analyze_react_components, find_inline_styles,
            find_style_patterns, analyze_style_values, find_unused_styles,
            build_source_graph, get_quick_wins, get_quick_wins_report,
            check_naming_consistency, scan_storybook, generate_story,
            generate_stories_batch, generate_storybook_theme, get_story_coverage
        )

        # Map tool names to functions
        tool_map = {
            'get_status': get_status,
            'list_projects': list_projects,
            'create_project': create_project,
            'get_project': get_project,
            'extract_tokens': extract_tokens,
            'extract_components': extract_components,
            'generate_component_code': generate_component_code,
            'sync_tokens_to_file': sync_tokens_to_file,
            'get_sync_history': get_sync_history,
            'get_activity': get_activity,
            'ingest_css_tokens': ingest_css_tokens,
            'ingest_scss_tokens': ingest_scss_tokens,
            'ingest_tailwind_tokens': ingest_tailwind_tokens,
            'ingest_json_tokens': ingest_json_tokens,
            'merge_tokens': merge_tokens,
            'export_tokens': export_tokens,
            'validate_tokens': validate_tokens,
            'discover_project': discover_project,
            'analyze_react_components': analyze_react_components,
            'find_inline_styles': find_inline_styles,
            'find_style_patterns': find_style_patterns,
            'analyze_style_values': analyze_style_values,
            'find_unused_styles': find_unused_styles,
            'build_source_graph': build_source_graph,
            'get_quick_wins': get_quick_wins,
            'get_quick_wins_report': get_quick_wins_report,
            'check_naming_consistency': check_naming_consistency,
            'scan_storybook': scan_storybook,
            'generate_story': generate_story,
            'generate_stories_batch': generate_stories_batch,
            'generate_storybook_theme': generate_storybook_theme,
            'get_story_coverage': get_story_coverage,
        }

        # Get the tool function
        tool_func = tool_map.get(tool_name)
        if not tool_func:
            raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found")

        # Execute tool
        result = await tool_func(**params)

        # Log execution
        ActivityLog.log(
            action="mcp_tool_executed",
            entity_type="tool",
            entity_id=tool_name,
            details={"params": list(params.keys())}
        )

        return JSONResponse(content={"success": True, "result": result})

    except Exception as e:
        ActivityLog.log(
            action="mcp_tool_failed",
            entity_type="tool",
            entity_id=tool_name,
            details={"error": str(e)}
        )
        raise HTTPException(status_code=500, detail=str(e))


# === MCP Integration Endpoints ===

class IntegrationCreate(BaseModel):
    """Create/Update integration configuration"""
    integration_type: str  # figma, jira, confluence, sequential-thinking
    config: Dict[str, Any]  # Encrypted in database
    enabled: bool = True


class IntegrationUpdate(BaseModel):
    """Update integration"""
    config: Optional[Dict[str, Any]] = None
    enabled: Optional[bool] = None


@app.get("/api/mcp/integrations")
async def list_all_integrations():
    """List all available integration types and their health status."""
    from storage.database import get_connection

    try:
        with get_connection() as conn:
            health_rows = conn.execute(
                "SELECT * FROM integration_health ORDER BY integration_type"
            ).fetchall()

            integrations = []
            for row in health_rows:
                integrations.append({
                    "integration_type": row["integration_type"],
                    "is_healthy": bool(row["is_healthy"]),
                    "failure_count": row["failure_count"],
                    "last_failure_at": row["last_failure_at"],
                    "last_success_at": row["last_success_at"],
                    "circuit_open_until": row["circuit_open_until"]
                })

            return {"integrations": integrations}
    except Exception as e:
        # Table may not exist yet
        return {
            "integrations": [
                {"integration_type": "figma", "is_healthy": True, "failure_count": 0},
                {"integration_type": "jira", "is_healthy": True, "failure_count": 0},
                {"integration_type": "confluence", "is_healthy": True, "failure_count": 0},
                {"integration_type": "sequential-thinking", "is_healthy": True, "failure_count": 0}
            ],
            "note": "Integration tables not yet initialized"
        }


@app.get("/api/projects/{project_id}/integrations")
async def list_project_integrations(
    project_id: str,
    user_id: Optional[int] = Query(None, description="Filter by user ID")
):
    """List integrations configured for a project."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    from storage.database import get_connection

    try:
        with get_connection() as conn:
            if user_id:
                rows = conn.execute(
                    """
                    SELECT id, integration_type, enabled, created_at, updated_at, last_used_at
                    FROM project_integrations
                    WHERE project_id = ? AND user_id = ?
                    ORDER BY integration_type
                    """,
                    (project_id, user_id)
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT id, user_id, integration_type, enabled, created_at, updated_at, last_used_at
                    FROM project_integrations
                    WHERE project_id = ?
                    ORDER BY integration_type
                    """,
                    (project_id,)
                ).fetchall()

            return {"integrations": [dict(row) for row in rows]}
    except Exception as e:
        return {"integrations": [], "error": str(e)}


@app.post("/api/projects/{project_id}/integrations")
async def create_integration(
    project_id: str,
    integration: IntegrationCreate,
    user_id: int = Query(..., description="User ID for user-scoped integration")
):
    """Create or update integration for a project (user-scoped)."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    from storage.database import get_connection
    from dss_mcp.config import mcp_config

    # Encrypt config
    config_json = json.dumps(integration.config)
    cipher = mcp_config.get_cipher()
    if cipher:
        encrypted_config = cipher.encrypt(config_json.encode()).decode()
    else:
        encrypted_config = config_json  # Store unencrypted if no key

    try:
        with get_connection() as conn:
            # Upsert
            conn.execute(
                """
                INSERT INTO project_integrations (project_id, user_id, integration_type, config, enabled, updated_at)
                VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
                ON CONFLICT(project_id, user_id, integration_type)
                DO UPDATE SET config = excluded.config, enabled = excluded.enabled, updated_at = CURRENT_TIMESTAMP
                """,
                (project_id, user_id, integration.integration_type, encrypted_config, integration.enabled)
            )

            ActivityLog.log(
                action="integration_configured",
                entity_type="integration",
                entity_id=integration.integration_type,
                project_id=project_id,
                details={"user_id": user_id, "enabled": integration.enabled}
            )

            return {
                "success": True,
                "integration_type": integration.integration_type,
                "enabled": integration.enabled
            }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.put("/api/projects/{project_id}/integrations/{integration_type}")
async def update_integration(
    project_id: str,
    integration_type: str,
    update: IntegrationUpdate,
    user_id: int = Query(..., description="User ID")
):
    """Update an existing integration."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    from storage.database import get_connection
    from dss_mcp.config import mcp_config

    try:
        with get_connection() as conn:
            updates = []
            params = []

            if update.config is not None:
                config_json = json.dumps(update.config)
                cipher = mcp_config.get_cipher()
                if cipher:
                    encrypted_config = cipher.encrypt(config_json.encode()).decode()
                else:
                    encrypted_config = config_json
                updates.append("config = ?")
                params.append(encrypted_config)

            if update.enabled is not None:
                updates.append("enabled = ?")
                params.append(update.enabled)

            if not updates:
                return {"success": False, "message": "No updates provided"}

            updates.append("updated_at = CURRENT_TIMESTAMP")
            params.extend([project_id, user_id, integration_type])

            conn.execute(
                f"""
                UPDATE project_integrations
                SET {', '.join(updates)}
                WHERE project_id = ? AND user_id = ? AND integration_type = ?
                """,
                params
            )

            return {"success": True, "integration_type": integration_type}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.delete("/api/projects/{project_id}/integrations/{integration_type}")
async def delete_integration(
    project_id: str,
    integration_type: str,
    user_id: int = Query(..., description="User ID")
):
    """Delete an integration configuration."""
    if not Projects.get(project_id):
        raise HTTPException(status_code=404, detail="Project not found")

    from storage.database import get_connection

    try:
        with get_connection() as conn:
            result = conn.execute(
                """
                DELETE FROM project_integrations
                WHERE project_id = ? AND user_id = ? AND integration_type = ?
                """,
                (project_id, user_id, integration_type)
            )

            if result.rowcount == 0:
                raise HTTPException(status_code=404, detail="Integration not found")

            ActivityLog.log(
                action="integration_deleted",
                entity_type="integration",
                entity_id=integration_type,
                project_id=project_id,
                details={"user_id": user_id}
            )

            return {"success": True}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/mcp/tools")
async def list_mcp_tools(include_details: bool = Query(False, description="Include full tool schemas")):
    """List all available MCP tools via unified handler."""
    from dss_mcp.handler import get_mcp_handler

    handler = get_mcp_handler()
    return handler.list_tools(include_details=include_details)


@app.get("/api/mcp/tools/{tool_name}")
async def get_mcp_tool_info(tool_name: str):
    """Get detailed information about a specific MCP tool."""
    from dss_mcp.handler import get_mcp_handler

    handler = get_mcp_handler()
    info = handler.get_tool_info(tool_name)

    if not info:
        raise HTTPException(status_code=404, detail=f"Tool not found: {tool_name}")

    return info


class MCPToolExecuteRequest(BaseModel):
    """Request to execute an MCP tool"""
    arguments: Dict[str, Any]
    project_id: str
    user_id: Optional[int] = 1


@app.post("/api/mcp/tools/{tool_name}/execute")
async def execute_mcp_tool(tool_name: str, request: MCPToolExecuteRequest):
    """
    Execute an MCP tool via unified handler.

    All tool executions go through the central MCPHandler which:
    - Validates tool existence
    - Checks integration configurations
    - Applies circuit breaker protection
    - Logs execution metrics
    """
    from dss_mcp.handler import get_mcp_handler, MCPContext

    handler = get_mcp_handler()

    # Create execution context
    context = MCPContext(
        project_id=request.project_id,
        user_id=request.user_id
    )

    # Execute tool
    result = await handler.execute_tool(
        tool_name=tool_name,
        arguments=request.arguments,
        context=context
    )

    # Log to activity
    ActivityLog.log(
        action="mcp_tool_executed",
        entity_type="tool",
        entity_id=tool_name,
        project_id=request.project_id,
        details={
            "success": result.success,
            "duration_ms": result.duration_ms,
            "error": result.error
        }
    )

    return result.to_dict()


@app.get("/api/mcp/status")
async def get_mcp_status():
    """Get MCP server status and configuration."""
    from dss_mcp.config import mcp_config, integration_config, validate_config

    warnings = validate_config()

    return {
        "server": {
            "host": mcp_config.HOST,
            "port": mcp_config.PORT,
            "encryption_enabled": bool(mcp_config.ENCRYPTION_KEY),
            "context_cache_ttl": mcp_config.CONTEXT_CACHE_TTL
        },
        "integrations": {
            "figma": bool(integration_config.FIGMA_TOKEN),
            "anthropic": bool(integration_config.ANTHROPIC_API_KEY),
            "jira_default": bool(integration_config.JIRA_URL),
            "confluence_default": bool(integration_config.CONFLUENCE_URL)
        },
        "circuit_breaker": {
            "failure_threshold": mcp_config.CIRCUIT_BREAKER_FAILURE_THRESHOLD,
            "timeout_seconds": mcp_config.CIRCUIT_BREAKER_TIMEOUT_SECONDS
        },
        "warnings": warnings
    }


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"
    )
