"""API007: Endpoint has no typed response.

File-level checker that flags API endpoints without typed responses across
FastAPI, Express, and Next.js. Untyped responses prevent code generation,
break IDE autocompletion for consumers, and allow response shape drift.

This is a free-tier checker — it works per-file without cross-file analysis.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import List

from stablestack.checkers.base import BaseChecker
from stablestack.models import Finding, Severity, Tier


class UntypedResponseChecker(BaseChecker):
    """Checks for API endpoints that return untyped responses."""

    rule_id = "API007"
    rule_name = "untyped-api-response"
    description = "API endpoint has no typed response — consumers can't rely on the response shape."
    why = (
        "Untyped API responses prevent TypeScript/OpenAPI code generation, break "
        "IDE autocompletion for consumers, and let the response shape drift silently. "
        "Add response_model (FastAPI), res.json<Type>() (Express), or "
        "NextResponse.json<Type>() (Next.js) to every endpoint."
    )
    severity = Severity.WARNING
    tier = Tier.RECOMMENDED
    file_extensions = {".py", ".ts", ".tsx", ".js", ".jsx"}

    SKIP_PATHS = {"/health", "/healthz", "/ready", "/docs", "/openapi", "/redoc", "/metrics"}

    # ── FastAPI ─────────────────────────────────────────────────────────

    _fastapi_route = re.compile(
        r'@(?:app|router)\.(get|post|put|patch|delete)\s*\(\s*["\']([^"\']+)["\']'
    )

    # ── Express ─────────────────────────────────────────────────────────

    _express_route = re.compile(
        r'(?:app|router)\.(get|post|put|patch|delete)\s*\(\s*["\']([^"\']+)["\']'
    )
    _typed_res_json = re.compile(r'res\.json\s*<\s*\w+')

    # ── Next.js App Router ──────────────────────────────────────────────

    _nextjs_handler = re.compile(
        r'export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\s*\('
    )
    _typed_next_response = re.compile(r'NextResponse\.json\s*<\s*\w+')

    def check_file(self, path: Path, content: str) -> List[Finding]:
        if path.suffix == ".py":
            return self._check_python(path, content)
        return self._check_typescript(path, content)

    def _check_python(self, path: Path, content: str) -> List[Finding]:
        findings: List[Finding] = []
        lines = content.split("\n")

        for i, line in enumerate(lines):
            match = self._fastapi_route.search(line)
            if not match:
                continue

            method, ep_path = match.groups()

            if any(skip in ep_path for skip in self.SKIP_PATHS):
                continue

            # Check next few lines for response_model or redirect
            context = "\n".join(lines[i : min(i + 5, len(lines))])
            if "response_model" in context or "RedirectResponse" in context:
                continue

            findings.append(
                self.create_finding(
                    path=path,
                    line=i + 1,
                    column=match.start() + 1,
                    code=line.strip(),
                    message=f"{method.upper()} {ep_path} has no typed response",
                    fix_hint=(
                        "Add response_model=YourPydanticModel to the route decorator "
                        "to enable OpenAPI generation and client type safety."
                    ),
                )
            )

        return findings

    def _check_typescript(self, path: Path, content: str) -> List[Finding]:
        findings: List[Finding] = []
        lines = content.split("\n")
        path_str = str(path)

        # Next.js App Router handlers
        if "/app/api/" in path_str or "\\app\\api\\" in path_str:
            findings.extend(self._check_nextjs_handlers(path, lines))

        # Express routes
        findings.extend(self._check_express_routes(path, lines))

        return findings

    def _check_express_routes(self, path: Path, lines: List[str]) -> List[Finding]:
        findings: List[Finding] = []

        for i, line in enumerate(lines):
            match = self._express_route.search(line)
            if not match:
                continue

            method, ep_path = match.groups()
            if any(skip in ep_path for skip in self.SKIP_PATHS):
                continue

            # Scan forward in the handler for typed res.json<Type>()
            handler_end = min(i + 30, len(lines))
            handler_text = "\n".join(lines[i:handler_end])

            if self._typed_res_json.search(handler_text):
                continue

            findings.append(
                self.create_finding(
                    path=path,
                    line=i + 1,
                    column=match.start() + 1,
                    code=line.strip(),
                    message=f"{method.upper()} {ep_path} has no typed response",
                    fix_hint=(
                        "Use res.json<YourType>(data) to type the response, "
                        "or define a return type for the handler."
                    ),
                )
            )

        return findings

    def _check_nextjs_handlers(self, path: Path, lines: List[str]) -> List[Finding]:
        findings: List[Finding] = []

        for i, line in enumerate(lines):
            match = self._nextjs_handler.search(line)
            if not match:
                continue

            method = match.group(1)

            # Scan forward for NextResponse.json<Type>()
            handler_end = min(i + 30, len(lines))
            handler_text = "\n".join(lines[i:handler_end])

            if self._typed_next_response.search(handler_text):
                continue

            findings.append(
                self.create_finding(
                    path=path,
                    line=i + 1,
                    column=match.start() + 1,
                    code=line.strip(),
                    message=f"{method} handler has no typed response",
                    fix_hint=(
                        "Use NextResponse.json<YourType>(data) to type the response."
                    ),
                )
            )

        return findings
