"""
JanexAI MCP Server

Exposes code analysis tools to Cursor agents via the Model Context Protocol.
This is a thin remote client that calls the JanexAI Django API.

Tools:
    - initialize_repo: Ingest + full deep scan a GitHub repository (one-time setup)
    - scan_files: Incrementally scan changed files for security/quality issues
    - get_fix_prompts: Retrieve fix prompts from the latest report

Usage (stdio, default for Cursor):
    janex-mcp

Configuration (.cursor/mcp.json):
    {
      "mcpServers": {
        "janex": {
          "command": "janex-mcp",
          "env": {
            "JANEX_API_TOKEN": "jnx_..."
          }
        }
      }
    }

Users who have already run `janex login` don't need to set JANEX_API_TOKEN --
the MCP server reads from ~/.janex/config.json automatically.
"""

import os
import json
import logging
from typing import Any

import requests
from mcp.server.fastmcp import FastMCP

from .main import get_api_url, get_api_token, parse_repo_url

logger = logging.getLogger(__name__)


# =============================================================================
# API Client
# =============================================================================

class JanexAPIClient:
    """Thin client for the JanexAI Django API."""

    def __init__(self, api_url: str, api_token: str):
        self.api_url = api_url.rstrip("/")
        self.api_token = api_token
        self.session = requests.Session()
        self.session.headers.update({
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_token}",
        })

    def _url(self, path: str) -> str:
        return f"{self.api_url}/api{path}"

    def post(self, path: str, data: dict | None = None, timeout: int = 600) -> dict:
        """POST request returning parsed JSON."""
        resp = self.session.post(self._url(path), json=data or {}, timeout=timeout)
        resp.raise_for_status()
        return resp.json()

    def get(self, path: str, timeout: int = 30) -> dict:
        """GET request returning parsed JSON."""
        resp = self.session.get(self._url(path), timeout=timeout)
        resp.raise_for_status()
        return resp.json()


# =============================================================================
# MCP Server
# =============================================================================

mcp = FastMCP(
    "JanexAI",
    description="AI-powered code security and quality analysis for Cursor agents",
)


def _get_client() -> JanexAPIClient:
    """
    Get the API client using the shared CLI config.

    Token resolution order:
    1. JANEX_API_TOKEN environment variable
    2. ~/.janex/config.json (set by `janex login`)
    """
    api_url = get_api_url()
    api_token = get_api_token()

    if not api_token:
        raise ValueError(
            "No API token found. Either:\n"
            "  1. Run `janex login` to authenticate, or\n"
            "  2. Set JANEX_API_TOKEN in your .cursor/mcp.json env"
        )

    # Enforce HTTPS in non-local environments to protect the API token
    if not api_url.startswith("https://") and "localhost" not in api_url and "127.0.0.1" not in api_url:
        raise ValueError(
            f"Refusing to send API token over insecure connection: {api_url}. "
            "Use HTTPS or localhost for development."
        )

    return JanexAPIClient(api_url=api_url, api_token=api_token)


# =============================================================================
# Tool: initialize_repo
# =============================================================================

@mcp.tool()
def initialize_repo(repo_url: str) -> str:
    """
    Initialize a GitHub repository for analysis (one-time setup).

    This ingests the repository and runs a full deep scan (Phase 1-6),
    caching the codebase analysis for fast subsequent scans.

    Takes ~3 minutes. Run this once before using scan_files.

    Args:
        repo_url: GitHub repository URL or owner/repo format
                  (e.g., "https://github.com/user/repo" or "user/repo")

    Returns:
        JSON with repo_id to use in subsequent scan_files calls.
    """
    client = _get_client()

    # Parse owner/repo from URL (reuses CLI's parser)
    try:
        owner, repo = parse_repo_url(repo_url)
    except Exception as e:
        return json.dumps({"error": str(e)})

    # Step 1: Ingest the repository
    try:
        ingest_result = client.post("/repositories/ingest/", {
            "owner": owner,
            "repo": repo,
        })
    except requests.exceptions.HTTPError as e:
        return json.dumps({
            "error": f"Ingestion failed: {e.response.text if e.response else str(e)}"
        })

    repo_id = ingest_result.get("id")
    if not repo_id:
        return json.dumps({"error": "No repository ID returned", "details": ingest_result})

    already_exists = ingest_result.get("already_exists", False)

    # Step 2: Run full deep scan (caches CodebaseAnalyzer output)
    if not already_exists:
        try:
            client.post(
                f"/repositories/{repo_id}/analyze/",
                {"use_claude": True},
                timeout=600,  # 10 minutes for full scan
            )
        except requests.exceptions.HTTPError as e:
            return json.dumps({
                "repo_id": repo_id,
                "warning": f"Ingestion succeeded but analysis failed: {e.response.text if e.response else str(e)}",
                "message": "You can still use scan_files, but the first call will be slower (no cached codebase analysis).",
            })

    return json.dumps({
        "repo_id": repo_id,
        "full_name": f"{owner}/{repo}",
        "status": "ready",
        "message": f"Repository {owner}/{repo} is initialized. Use scan_files with repo_id='{repo_id}' to analyze changed files.",
    })


# =============================================================================
# Tool: scan_files
# =============================================================================

@mcp.tool()
def scan_files(repo_id: str, files: list[dict]) -> str:
    """
    Scan changed files for security vulnerabilities and code quality issues.

    Uses the full 5-agent LLM pipeline for high accuracy:
    - Incrementally updates the vector index with changed files
    - Runs deterministic security + quality detectors
    - Validates findings with LLM to filter false positives
    - Generates actionable fix prompts you can apply directly

    Takes ~30-60 seconds. The agent should wait for the result.

    Args:
        repo_id: Repository UUID from initialize_repo
        files: List of changed files, each with:
               - path (str): File path relative to repo root
               - content (str): Full file content (omit for deleted files)
               - action (str): "created", "modified", or "deleted"

    Returns:
        JSON with validated findings and fix prompts.
    """
    client = _get_client()

    try:
        result = client.post(
            f"/repositories/{repo_id}/scan-files/",
            {"files": files},
            timeout=300,  # 5 minutes
        )
    except requests.exceptions.HTTPError as e:
        return json.dumps({
            "error": f"Scan failed: {e.response.text if e.response else str(e)}"
        })

    # Format the response for the agent
    findings = result.get("findings", [])
    fix_prompts = result.get("fix_prompts", [])
    stats = result.get("stats", {})

    if not findings and not fix_prompts:
        return json.dumps({
            "status": "clean",
            "message": "No security or quality issues found in the changed files.",
            "stats": stats,
        })

    return json.dumps({
        "status": "issues_found",
        "findings_count": len(findings),
        "findings": findings,
        "fix_prompts": fix_prompts,
        "stats": stats,
    })


# =============================================================================
# Tool: get_fix_prompts
# =============================================================================

@mcp.tool()
def get_fix_prompts(repo_id: str, report_id: str = "") -> str:
    """
    Get fix prompts from the latest analysis report.

    Useful for retrieving previously generated fix prompts without
    re-running the scan.

    Args:
        repo_id: Repository UUID from initialize_repo
        report_id: Optional specific report UUID. If empty, uses the latest report.

    Returns:
        JSON with the fix prompts array.
    """
    client = _get_client()

    try:
        if report_id:
            result = client.get(f"/reports/{report_id}/")
        else:
            # Get latest report for this repo
            reports = client.get(f"/repositories/{repo_id}/reports/")
            reports_list = reports.get("reports", [])
            if not reports_list:
                return json.dumps({
                    "error": "No reports found for this repository. Run scan_files first."
                })
            latest_id = reports_list[0].get("id")
            result = client.get(f"/reports/{latest_id}/")
    except requests.exceptions.HTTPError as e:
        return json.dumps({
            "error": f"Failed to get report: {e.response.text if e.response else str(e)}"
        })

    # Extract fix prompts from the analysis result
    analysis = result.get("analysis_result", {})
    fix_prompts = analysis.get("fix_prompts", [])

    if not fix_prompts:
        return json.dumps({
            "message": "No fix prompts in the latest report.",
            "report_id": result.get("id"),
        })

    return json.dumps({
        "report_id": result.get("id"),
        "fix_prompts": fix_prompts,
        "count": len(fix_prompts),
    })


# =============================================================================
# Entry point
# =============================================================================

def main():
    """Run the MCP server (stdio transport for Cursor)."""
    import argparse

    parser = argparse.ArgumentParser(description="JanexAI MCP Server")
    parser.add_argument(
        "--api-url",
        default=None,
        help="JanexAI API URL override (default: from config or https://api.janexai.dev)",
    )
    parser.add_argument(
        "--transport",
        choices=["stdio", "sse"],
        default="stdio",
        help="Transport type (default: stdio)",
    )
    args = parser.parse_args()

    # Override API URL if provided via CLI flag
    if args.api_url:
        os.environ["JANEX_API_URL"] = args.api_url

    # Run the MCP server
    mcp.run(transport=args.transport)


if __name__ == "__main__":
    main()
