"""Compliance-verification MCP server.

Provides tools for FDA 21 CFR Part 11 compliance verification workflows,
including requirement change impact analysis and verification ticket creation.

Replaces the former compliance-verification Claude Code plugin.
Uses elspais for requirement data and the Linear GraphQL API for tickets.
"""

from __future__ import annotations

import json
import os
import re
import subprocess

import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("compliance", instructions="FDA 21 CFR Part 11 compliance verification tools")

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

LINEAR_API = "https://api.linear.app/graphql"


def _linear_token() -> str:
    token = os.environ.get("LINEAR_API_TOKEN", "")
    if not token:
        raise ValueError("LINEAR_API_TOKEN environment variable not set")
    return token


def _linear_query(query: str, variables: dict | None = None) -> dict:
    """Execute a Linear GraphQL query."""
    payload: dict = {"query": query}
    if variables:
        payload["variables"] = variables
    resp = httpx.post(
        LINEAR_API,
        json=payload,
        headers={"Authorization": _linear_token(), "Content-Type": "application/json"},
        timeout=30,
    )
    resp.raise_for_status()
    data = resp.json()
    if "errors" in data:
        raise ValueError(f"Linear API error: {data['errors']}")
    return data.get("data", {})


# ---------------------------------------------------------------------------
# Subsystem definitions for impact analysis
# ---------------------------------------------------------------------------

SUBSYSTEMS = {
    "Supabase (Database & Auth)": {
        "keywords": [
            "database", "schema", "rls", "postgres", "auth", "user", "role",
            "permission", "audit", "event sourcing", "migration", "trigger",
        ],
        "verification": [
            "Verify RLS policies still enforce correct access",
            "Check event store triggers for audit completeness",
            "Run database migration tests",
            "Verify ALCOA+ data integrity requirements",
        ],
    },
    "Mobile App (Flutter)": {
        "keywords": ["mobile", "app", "flutter", "dart", "offline", "sync", "patient", "diary", "ui"],
        "verification": [
            "Test offline data collection still works",
            "Verify sync mechanism handles new requirement",
            "Run flutter analyze and flutter test",
            "Check accessibility compliance",
        ],
    },
    "Web Portal": {
        "keywords": ["web", "portal", "sponsor", "dashboard", "investigator", "analyst"],
        "verification": [
            "Test role-based access controls",
            "Verify sponsor data isolation",
            "Check portal UI reflects requirement changes",
        ],
    },
    "CI/CD Pipeline": {
        "keywords": ["workflow", "deployment", "build", "test", "automation", "ci", "cd", "pipeline"],
        "verification": [
            "Verify CI validation catches non-compliant changes",
            "Check deployment gates still enforce quality",
            "Run full CI pipeline locally",
        ],
    },
    "Compliance & Documentation": {
        "keywords": ["fda", "alcoa", "audit trail", "traceability", "validation", "compliance", "cfr"],
        "verification": [
            "Review traceability matrix for completeness",
            "Verify audit trail captures all required events",
            "Check ALCOA+ principles are maintained",
            "Update compliance documentation",
        ],
    },
    "Infrastructure": {
        "keywords": ["docker", "container", "hosting", "deploy", "cdn", "infrastructure"],
        "verification": [
            "Verify container health checks",
            "Check deployment configuration",
            "Test infrastructure changes in dev environment",
        ],
    },
    "Secrets Management (Doppler)": {
        "keywords": ["secrets", "credentials", "token", "env var", "doppler", "api key"],
        "verification": [
            "Verify no secrets exposed in requirement changes",
            "Check Doppler configuration aligns with changes",
        ],
    },
    "Backup & Recovery": {
        "keywords": ["backup", "recovery", "retention", "restore", "archive"],
        "verification": [
            "Verify backup procedures cover new data",
            "Test recovery procedures",
            "Check retention policies comply with FDA requirements",
        ],
    },
}


def _analyze_subsystem_impact(text: str) -> list[dict]:
    """Analyze which subsystems are impacted by requirement text."""
    text_lower = text.lower()
    impacted = []

    # Security/RBAC requirements impact all access-controlled subsystems
    is_security = any(kw in text_lower for kw in ["security", "rbac", "access control", "authentication"])

    for name, config in SUBSYSTEMS.items():
        keywords = config["keywords"]
        matched = [kw for kw in keywords if kw in text_lower]

        if matched or (is_security and "auth" in " ".join(keywords).lower()):
            impacted.append({
                "subsystem": name,
                "matched_keywords": matched,
                "verification_steps": config["verification"],
            })

    return impacted


# ---------------------------------------------------------------------------
# MCP Tools
# ---------------------------------------------------------------------------


@mcp.tool()
def create_verification_ticket(
    req_id: str,
    old_hash: str,
    new_hash: str,
    title: str = "",
    team_id: str | None = None,
) -> str:
    """Create a verification ticket when a requirement changes.

    Args:
        req_id: The requirement ID that changed (e.g., REQ-p00001).
        old_hash: Previous content hash.
        new_hash: New content hash.
        title: Requirement title (auto-fetched if empty).
        team_id: Linear team UUID. Defaults to LINEAR_TEAM_ID env var.
    """
    team = team_id or os.environ.get("LINEAR_TEAM_ID", "")
    if not team:
        return "Error: LINEAR_TEAM_ID not set and no team_id provided"

    # Get requirement details if title not provided
    if not title:
        try:
            result = subprocess.run(
                ["elspais", "get", req_id, "--json"],
                capture_output=True, text=True, timeout=10,
            )
            if result.returncode == 0:
                req_data = json.loads(result.stdout)
                title = req_data.get("title", req_id)
        except Exception:
            title = req_id

    # Analyze subsystem impact
    impact = _analyze_subsystem_impact(title)
    subsystem_checklist = "\n".join(
        f"- [ ] **{s['subsystem']}**" for s in impact
    ) or "- [ ] No specific subsystem impact detected - review manually"

    description = f"""## Requirement Change Verification

**Requirement**: {req_id} - {title}
**Hash Change**: `{old_hash[:8]}` -> `{new_hash[:8]}`

### Verification Steps

1. **Review the requirement change**
   ```
   git log --all -p -- 'spec/*' | grep -A 20 '{req_id}'
   ```

2. **Find all implementations**
   ```
   git grep -l '{req_id}' -- '*.dart' '*.sql' '*.py' '*.ts'
   ```

3. **Verify compliance of each implementation**
   - [ ] Implementation matches updated requirement
   - [ ] Tests cover updated assertions
   - [ ] Audit trail captures changes correctly
   - [ ] ALCOA+ principles maintained

4. **Mark as verified**
   Update the requirement hash in tracking after verification.

### Subsystem Impact
{subsystem_checklist}

### Verification Details
{chr(10).join(f"#### {s['subsystem']}" + chr(10) + chr(10).join(f"- [ ] {v}" for v in s['verification_steps']) for s in impact)}
"""

    mutation = """
    mutation($title: String!, $teamId: String!, $description: String!, $priority: Int) {
        issueCreate(input: {
            title: $title
            teamId: $teamId
            description: $description
            priority: $priority
        }) {
            success
            issue { id identifier url title }
        }
    }
    """
    data = _linear_query(mutation, {
        "title": f"[Verification] {req_id}: {title}",
        "teamId": team,
        "description": description,
        "priority": 2,  # High priority for compliance
    })

    issue = data.get("issueCreate", {}).get("issue", {})
    return json.dumps({
        "created": True,
        "req_id": req_id,
        "ticket": issue.get("identifier"),
        "url": issue.get("url"),
        "subsystems_impacted": [s["subsystem"] for s in impact],
        "hash_change": f"{old_hash[:8]} -> {new_hash[:8]}",
    }, indent=2)


@mcp.tool()
def analyze_subsystem_impact(req_id: str) -> str:
    """Analyze which subsystems are affected by a requirement.

    Args:
        req_id: Requirement ID to analyze (e.g., REQ-p00042).
    """
    try:
        result = subprocess.run(
            ["elspais", "get", req_id, "--json"],
            capture_output=True, text=True, timeout=10,
        )
        if result.returncode != 0:
            return f"Error: Could not fetch {req_id}"
        req_data = json.loads(result.stdout)
    except (FileNotFoundError, json.JSONDecodeError) as e:
        return f"Error: {e}"

    title = req_data.get("title", "")
    assertions = req_data.get("assertions", [])
    full_text = title + " " + " ".join(a.get("text", "") for a in assertions)

    impact = _analyze_subsystem_impact(full_text)

    return json.dumps({
        "req_id": req_id,
        "title": title,
        "total_subsystems": len(SUBSYSTEMS),
        "impacted_count": len(impact),
        "impacted_subsystems": impact,
    }, indent=2)


@mcp.tool()
def get_changed_requirements() -> str:
    """Detect requirements whose content hash has changed since last verification.

    Returns requirements where the current hash differs from the stored hash.
    """
    try:
        result = subprocess.run(
            ["elspais", "changes", "--json"],
            capture_output=True, text=True, timeout=30,
        )
        if result.returncode != 0:
            # Try alternate command
            result = subprocess.run(
                ["elspais", "validate", "--json"],
                capture_output=True, text=True, timeout=30,
            )
        if result.returncode == 0 and result.stdout.strip():
            changes = json.loads(result.stdout)
            return json.dumps(changes, indent=2)
    except (FileNotFoundError, json.JSONDecodeError) as e:
        return f"Error detecting changes: {e}"

    return json.dumps({"changed": [], "note": "No changes detected or elspais not available"})


def main() -> None:
    """Start the compliance verification MCP server."""
    mcp.run()
