"""Tests for API006 Cross-File Endpoint/Client Type Mismatch Checker."""

import pytest
from pathlib import Path

from stablestack.checkers.api.cross_file_type_mismatch import CrossFileTypeMismatchChecker


class TestAPI006:
    def setup_method(self):
        self.checker = CrossFileTypeMismatchChecker()

    def test_metadata(self):
        assert self.checker.rule_id == "API006"
        assert self.checker.is_project_checker is True

    def test_check_file_returns_empty(self, tmp_path):
        """check_file is a no-op for project checkers."""
        p = tmp_path / "dummy.py"
        p.write_text("x = 1")
        assert self.checker.check_file(p, "x = 1") == []

    def test_check_project_empty_dir(self, tmp_path):
        """No crash on empty directory."""
        findings = self.checker.check_project(tmp_path)
        assert findings == []

    # Note: untyped endpoint checks moved to API007 (test_api_untyped_response.py)

    # ── Check 2: Client type != endpoint type ───────────────────────────

    def test_client_type_mismatch(self, tmp_path):
        """Client type has different fields than endpoint type — finding."""
        # Backend
        api = tmp_path / "app.py"
        api.write_text("""\
from fastapi import FastAPI
from pydantic import BaseModel

class UserResponse(BaseModel):
    name: str
    email: str

app = FastAPI()

@app.get("/api/users", response_model=UserResponse)
async def get_users() -> UserResponse:
    pass
""")
        # Client type definition
        types_file = tmp_path / "types.ts"
        types_file.write_text("""\
interface UserDTO {
  name: string;
}
""")
        # Client call
        client = tmp_path / "client.ts"
        client.write_text("""\
const users: UserDTO = await axios.get<UserDTO>("/api/users");
""")
        findings = self.checker.check_project(tmp_path)
        mismatch = [f for f in findings if "doesn't match" in f.message]
        assert len(mismatch) == 1
        assert "email" in mismatch[0].message

    # ── Check 3: Client uses inline type ────────────────────────────────

    def test_client_inline_type(self, tmp_path):
        """Client uses inline type assertion — finding."""
        client = tmp_path / "client.ts"
        client.write_text("""\
const users = await fetch("/api/users").then(r => r.json()) as { name: string };
""")
        findings = self.checker.check_project(tmp_path)
        inline = [f for f in findings if "inline type" in f.message]
        assert len(inline) == 1

    # ── Check 4: Duplicate type definitions ─────────────────────────────

    def test_duplicate_type_in_multiple_files(self, tmp_path):
        """Same type name+fields in 2 files — finding."""
        f1 = tmp_path / "types1.ts"
        f1.write_text("""\
interface User {
  name: string;
  email: string;
}
""")
        f2 = tmp_path / "types2.ts"
        f2.write_text("""\
interface User {
  name: string;
  email: string;
}
""")
        findings = self.checker.check_project(tmp_path)
        dup = [f for f in findings if "defined identically" in f.message]
        assert len(dup) == 1
        assert "2 files" in dup[0].message

    # ── Check 5: FastAPI response_model != return type ──────────────────

    def test_fastapi_response_model_vs_return_type(self, tmp_path):
        """response_model=UserResponse but return type is dict — finding."""
        api = tmp_path / "app.py"
        api.write_text("""\
from fastapi import FastAPI
from pydantic import BaseModel

class UserResponse(BaseModel):
    name: str

app = FastAPI()

@app.get("/api/users", response_model=UserResponse)
async def get_users() -> dict:
    return {"name": "Alice"}
""")
        findings = self.checker.check_project(tmp_path)
        mismatch = [f for f in findings if "response_model=" in f.message]
        assert len(mismatch) == 1
        assert "dict" in mismatch[0].message

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

    def test_nextjs_app_router_detection(self, tmp_path):
        """Detects Next.js App Router endpoint from file path (used for cross-ref)."""
        route_dir = tmp_path / "app" / "api" / "users" / "[id]"
        route_dir.mkdir(parents=True)
        route_file = route_dir / "route.ts"
        route_file.write_text("""\
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  return NextResponse.json<UserResponse>({ name: "Alice" });
}
""")
        # Define the endpoint's response type
        server_types = tmp_path / "server-types.ts"
        server_types.write_text("""\
interface UserResponse {
  name: string;
  email: string;
}
""")
        # Client uses a different type with different fields
        client_types = tmp_path / "client-types.ts"
        client_types.write_text("""\
interface UserDTO {
  name: string;
}
""")
        client = tmp_path / "client.ts"
        client.write_text("""\
const user = await axios.get<UserDTO>("/api/users/123");
""")
        findings = self.checker.check_project(tmp_path)
        # The endpoint should be collected for cross-referencing
        # (untyped check is now in API007, not duplicated here)
        mismatch = [f for f in findings if "doesn't match" in f.message]
        assert len(mismatch) == 1
        assert "email" in mismatch[0].message

    # ── URL param matching ──────────────────────────────────────────────

    def test_url_param_matching_across_styles(self):
        """Different URL param styles should normalize to the same segments."""
        normalize = CrossFileTypeMismatchChecker._normalize_url

        assert normalize("/api/users/:id") == normalize("/api/users/${id}")
        assert normalize("/api/users/:id") == normalize("/api/users/{id}")
        assert normalize("/api/users/:id") == normalize("/api/users/[id]")

    # ── No false positive when types match ──────────────────────────────

    def test_no_finding_when_types_match(self, tmp_path):
        """Client and endpoint use same type — no mismatch finding."""
        api = tmp_path / "app.py"
        api.write_text("""\
from fastapi import FastAPI
from pydantic import BaseModel

class UserResponse(BaseModel):
    name: str

app = FastAPI()

@app.get("/api/users", response_model=UserResponse)
async def get_users() -> UserResponse:
    pass
""")
        types_file = tmp_path / "types.ts"
        types_file.write_text("""\
interface UserResponse {
  name: string;
}
""")
        client = tmp_path / "client.ts"
        client.write_text("""\
const users = await axios.get<UserResponse>("/api/users");
""")
        findings = self.checker.check_project(tmp_path)
        mismatch = [f for f in findings if "doesn't match" in f.message]
        assert len(mismatch) == 0

    # ── Infrastructure endpoints skipped ────────────────────────────────

    def test_health_endpoint_skipped(self, tmp_path):
        """Health/readiness endpoints are not flagged."""
        api = tmp_path / "app.py"
        api.write_text("""\
from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
async def health():
    return {"status": "ok"}

@app.get("/healthz")
async def healthz():
    return {"status": "ok"}
""")
        findings = self.checker.check_project(tmp_path)
        assert len(findings) == 0
