"""Tests for the corpus and processing API routers."""

from __future__ import annotations

import tempfile
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from sltk.api.main import app
from sltk.data.core import Segment, SegmentList
from sltk.io.elan import write_eaf

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def client():
    return TestClient(app)


@pytest.fixture
def tmp_workspace(tmp_path):
    """Create a temp dir with .eaf files for workspace scanning."""
    eaf_dir = tmp_path / "ws"
    eaf_dir.mkdir()

    # Create a sample video-like placeholder
    (eaf_dir / "test.mp4").write_bytes(b"\x00" * 100)

    # Create sample .eaf file
    segs = SegmentList(
        [
            Segment(0.0, 1.0, "HELLO", tier="Gloss"),
            Segment(1.0, 2.0, "WORLD", tier="Gloss"),
            Segment(2.0, 3.5, "HELLO", tier="Gloss"),
        ]
    )
    write_eaf(segs, eaf_dir / "test.eaf")
    return eaf_dir


def _setup_workspace(client, ws_dir: Path):
    """Create a workspace from a directory."""
    resp = client.post(
        "/api/workspace/create",
        json={
            "name": "TestCorpus",
            "folder_path": str(ws_dir),
            "recursive": True,
        },
    )
    assert resp.status_code == 200, resp.text


def _cleanup_workspace(client):
    """Clean up the test workspace."""
    try:
        client.delete("/api/workspace/clear")
    except Exception:
        pass


# ---------------------------------------------------------------------------
# Corpus API tests
# ---------------------------------------------------------------------------


class TestCorpusAPI:
    def test_status_no_workspace(self, client):
        _cleanup_workspace(client)
        resp = client.get("/api/corpus/status")
        assert resp.status_code == 400

    def test_status_with_workspace(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            resp = client.get("/api/corpus/status")
            assert resp.status_code == 200
            data = resp.json()
            assert "file_count" in data
            assert "segment_count" in data
        finally:
            _cleanup_workspace(client)

    def test_rebuild(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            resp = client.post("/api/corpus/rebuild")
            assert resp.status_code == 200
            data = resp.json()
            assert data["status"] == "rebuilt"
            assert data["files_ingested"] >= 1
        finally:
            _cleanup_workspace(client)

    def test_summary(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.get("/api/corpus/summary")
            assert resp.status_code == 200
            data = resp.json()
            assert data["total_files"] >= 1
            assert data["total_segments"] >= 3
            assert data["vocabulary_size"] >= 2
        finally:
            _cleanup_workspace(client)

    def test_glosses(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.get("/api/corpus/glosses", params={"top_n": 10})
            assert resp.status_code == 200
            glosses = resp.json()
            assert len(glosses) > 0
            assert all("gloss" in g for g in glosses)
            assert all("frequency" in g for g in glosses)
        finally:
            _cleanup_workspace(client)

    def test_find_gloss(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.get("/api/corpus/gloss/HELLO")
            assert resp.status_code == 200
            results = resp.json()
            assert len(results) == 2
        finally:
            _cleanup_workspace(client)

    def test_gloss_stats(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.get("/api/corpus/gloss/HELLO/stats")
            assert resp.status_code == 200
            data = resp.json()
            assert data["count"] == 2
            assert data["mean_ms"] > 0
        finally:
            _cleanup_workspace(client)

    def test_search(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/search",
                json={"query": "HELLO", "limit": 10},
            )
            assert resp.status_code == 200
            results = resp.json()
            assert len(results) == 2
        finally:
            _cleanup_workspace(client)

    def test_concordance(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/concordance",
                json={
                    "target": "WORLD",
                    "context_window": 5,
                },
            )
            assert resp.status_code == 200
            results = resp.json()
            assert len(results) >= 1
            assert results[0]["target"] == "WORLD"
        finally:
            _cleanup_workspace(client)

    def test_ngrams(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/ngrams",
                json={"n": 2, "limit": 10},
            )
            assert resp.status_code == 200
            results = resp.json()
            assert len(results) > 0
        finally:
            _cleanup_workspace(client)

    def test_cooccurrence(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/cooccurrence",
                json={
                    "target": "HELLO",
                    "window": 3,
                    "limit": 10,
                },
            )
            assert resp.status_code == 200
            results = resp.json()
            assert len(results) > 0
        finally:
            _cleanup_workspace(client)

    def test_vocabulary(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.get(
                "/api/corpus/vocabulary",
                params={"limit": 100},
            )
            assert resp.status_code == 200
            data = resp.json()
            assert "vocabulary_size" in data
            assert "total_tokens" in data
            assert "frequencies" in data
            assert data["vocabulary_size"] >= 2
            assert data["total_tokens"] >= 3
        finally:
            _cleanup_workspace(client)

    def test_cooccurrence_matrix(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/cooccurrence-matrix",
                json={"top_n": 10, "window": 2},
            )
            assert resp.status_code == 200
            data = resp.json()
            assert "glosses" in data
            assert "matrix" in data
            assert "frequencies" in data
            n = len(data["glosses"])
            assert len(data["matrix"]) == n
        finally:
            _cleanup_workspace(client)

    def test_duration_analysis(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/duration-analysis",
                json={"limit": 50, "min_count": 1},
            )
            assert resp.status_code == 200
            data = resp.json()
            assert "items" in data
            assert "total_glosses" in data
            # With min_count=1, we should get glosses
            assert data["total_glosses"] >= 1
            for item in data["items"]:
                assert "gloss" in item
                assert "count" in item
                assert "mean_ms" in item
        finally:
            _cleanup_workspace(client)

    def test_minimal_pairs(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            client.post("/api/corpus/rebuild")
            resp = client.post(
                "/api/corpus/minimal-pairs",
                json={"threshold": 0.5, "max_pairs": 50},
            )
            assert resp.status_code == 200
            data = resp.json()
            assert "vocabulary_size" in data
            assert "total_pairs" in data
            assert "similarity_threshold" in data
            assert "pairs" in data
        finally:
            _cleanup_workspace(client)

    def test_vocabulary_no_workspace(self, client):
        _cleanup_workspace(client)
        resp = client.get("/api/corpus/vocabulary")
        assert resp.status_code == 400

    def test_workspace_param_not_found(self, client, tmp_workspace):
        _setup_workspace(client, tmp_workspace)
        try:
            resp = client.get(
                "/api/corpus/vocabulary",
                params={"workspace": "nonexistent_ws"},
            )
            assert resp.status_code == 404
        finally:
            _cleanup_workspace(client)


# ---------------------------------------------------------------------------
# Processing API tests
# ---------------------------------------------------------------------------


class TestUploadBatch:
    def test_upload_batch(self, client, tmp_path):
        _cleanup_workspace(client)
        (tmp_path / "a.mp4").write_bytes(b"\x00" * 100)
        (tmp_path / "b.mp4").write_bytes(b"\x00" * 100)
        try:
            resp = client.post(
                "/api/workspace/upload-batch",
                files=[
                    (
                        "files",
                        ("a.mp4", open(tmp_path / "a.mp4", "rb"), "video/mp4"),
                    ),
                    (
                        "files",
                        ("b.mp4", open(tmp_path / "b.mp4", "rb"), "video/mp4"),
                    ),
                ],
            )
            assert resp.status_code == 200
            data = resp.json()
            assert data["counts"]["videos"] >= 2
        finally:
            _cleanup_workspace(client)

    def test_upload_batch_rejects_bad_extension(self, client, tmp_path):
        _cleanup_workspace(client)
        (tmp_path / "bad.txt").write_bytes(b"hello")
        try:
            resp = client.post(
                "/api/workspace/upload-batch",
                files=[
                    (
                        "files",
                        ("bad.txt", open(tmp_path / "bad.txt", "rb"), "text/plain"),
                    ),
                ],
            )
            assert resp.status_code == 400
        finally:
            _cleanup_workspace(client)

    def test_upload_batch_empty(self, client):
        resp = client.post(
            "/api/workspace/upload-batch",
            files=[],
        )
        # FastAPI returns 422 for missing required field
        assert resp.status_code in (400, 422)


class TestDiscoverEndpoint:
    def test_discover_returns_files(self, client, tmp_workspace):
        """Discover returns videos and ELAN without creating a workspace."""
        _cleanup_workspace(client)
        try:
            resp = client.post(
                "/api/workspace/discover",
                json={
                    "folder_path": str(tmp_workspace),
                    "recursive": True,
                },
            )
            assert resp.status_code == 200
            data = resp.json()
            assert data["path"] == str(tmp_workspace)
            assert len(data["videos"]) >= 1
            assert len(data["elan_files"]) >= 1
            assert "counts" in data

            # Verify no workspace was created
            resp2 = client.get("/api/workspace/list")
            ws_names = [w["name"] for w in resp2.json().get("workspaces", [])]
            assert "TestDiscover" not in ws_names
        finally:
            _cleanup_workspace(client)

    def test_discover_nonexistent_path(self, client):
        resp = client.post(
            "/api/workspace/discover",
            json={
                "folder_path": "/nonexistent/path/xyz",
                "recursive": True,
            },
        )
        assert resp.status_code in (400, 403, 404, 422)


class TestCreateWithSelection:
    def test_create_with_selected_videos(self, client, tmp_path):
        """Only selected videos appear; unselected are detached."""
        _cleanup_workspace(client)
        ws_dir = tmp_path / "sel"
        ws_dir.mkdir()
        (ws_dir / "a.mp4").write_bytes(b"\x00" * 100)
        (ws_dir / "b.mp4").write_bytes(b"\x00" * 100)
        (ws_dir / "c.mp4").write_bytes(b"\x00" * 100)
        try:
            # Discover first to get actual paths
            disc = client.post(
                "/api/workspace/discover",
                json={"folder_path": str(ws_dir), "recursive": True},
            )
            all_video_paths = [v["path"] for v in disc.json()["videos"]]
            # Select only first two
            selected = all_video_paths[:2]

            resp = client.post(
                "/api/workspace/create",
                json={
                    "name": "SelectionTest",
                    "folder_path": str(ws_dir),
                    "recursive": True,
                    "selected_video_paths": selected,
                },
            )
            assert resp.status_code == 200
            data = resp.json()
            # Only selected videos should appear in matches + unmatched_videos
            visible_paths = {m["video"]["path"] for m in data["matches"]}
            visible_paths |= {v["path"] for v in data["unmatched_videos"]}
            for p in selected:
                assert p in visible_paths
            # The third should be detached (not visible)
            detached = set(all_video_paths) - set(selected)
            for p in detached:
                assert p not in visible_paths
        finally:
            _cleanup_workspace(client)

    def test_create_without_selection_includes_all(self, client, tmp_path):
        """When selected_video_paths is None, all videos are included."""
        _cleanup_workspace(client)
        ws_dir = tmp_path / "all"
        ws_dir.mkdir()
        (ws_dir / "x.mp4").write_bytes(b"\x00" * 100)
        (ws_dir / "y.mp4").write_bytes(b"\x00" * 100)
        try:
            resp = client.post(
                "/api/workspace/create",
                json={
                    "name": "AllVideos",
                    "folder_path": str(ws_dir),
                    "recursive": True,
                },
            )
            assert resp.status_code == 200
            data = resp.json()
            visible = {m["video"]["path"] for m in data["matches"]}
            visible |= {v["path"] for v in data["unmatched_videos"]}
            assert len(visible) == 2
        finally:
            _cleanup_workspace(client)


class TestIngestFromFilenames:
    def test_ingest_from_filenames(self, client, tmp_path):
        """Creates synthetic entries with pre-computed glosses."""
        _cleanup_workspace(client)
        ws_dir = tmp_path / "fn"
        ws_dir.mkdir()
        (ws_dir / "BOOK_signer1.mp4").write_bytes(b"\x00" * 100)
        (ws_dir / "CHAIR_signer2.mp4").write_bytes(b"\x00" * 100)
        try:
            _setup_workspace(client, ws_dir)

            # Discover to get actual video paths
            disc = client.post(
                "/api/workspace/discover",
                json={"folder_path": str(ws_dir), "recursive": True},
            )
            videos = disc.json()["videos"]
            entries = [{"video_path": v["path"], "gloss": v["stem"][:4]} for v in videos]

            resp = client.post(
                "/api/corpus/ingest-from-filenames",
                json={"entries": entries, "tier_name": "Gloss"},
            )
            assert resp.status_code == 200
            data = resp.json()
            assert data["created"] == 2
            assert data["skipped"] == 0
            assert len(data["results"]) == 2
            # Check glosses
            gloss_set = {r["gloss"] for r in data["results"]}
            assert "BOOK" in gloss_set
            assert "CHAI" in gloss_set
        finally:
            _cleanup_workspace(client)

    def test_ingest_from_filenames_empty_gloss(self, client, tmp_path):
        """Empty glosses are skipped."""
        _cleanup_workspace(client)
        ws_dir = tmp_path / "trunc"
        ws_dir.mkdir()
        (ws_dir / "AB.mp4").write_bytes(b"\x00" * 100)
        try:
            _setup_workspace(client, ws_dir)
            disc = client.post(
                "/api/workspace/discover",
                json={"folder_path": str(ws_dir), "recursive": True},
            )
            video_path = disc.json()["videos"][0]["path"]

            resp = client.post(
                "/api/corpus/ingest-from-filenames",
                json={
                    "entries": [{"video_path": video_path, "gloss": ""}],
                    "tier_name": "Gloss",
                },
            )
            assert resp.status_code == 200
            data = resp.json()
            assert data["created"] == 0
            assert data["skipped"] == 1
            result = data["results"][0]
            assert result["status"] == "empty"
        finally:
            _cleanup_workspace(client)


class TestProcessingAPI:
    def test_submit_job(self, client):
        resp = client.post(
            "/api/processing/submit",
            json={
                "video_paths": ["/tmp/test.mp4"],
                "type": "segments",
                "model": "SIGNREP",
            },
        )
        assert resp.status_code == 200
        data = resp.json()
        assert "job_id" in data
        assert data["status"] == "pending"
        assert data["total_videos"] == 1

    def test_get_job_status(self, client):
        # Submit first
        resp = client.post(
            "/api/processing/submit",
            json={
                "video_paths": ["/tmp/a.mp4", "/tmp/b.mp4"],
                "type": "both",
                "model": "SIGNREP-BSLCP",
            },
        )
        job_id = resp.json()["job_id"]

        # Then query
        resp = client.get(f"/api/processing/status/{job_id}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["job_id"] == job_id
        assert data["total_videos"] == 2
        assert data["type"] == "both"

    def test_get_nonexistent_job(self, client):
        resp = client.get("/api/processing/status/nonexistent-id")
        assert resp.status_code == 404

    def test_list_jobs(self, client):
        # Submit a job
        client.post(
            "/api/processing/submit",
            json={
                "video_paths": ["/tmp/test.mp4"],
                "type": "segments",
            },
        )

        resp = client.get("/api/processing/jobs")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] >= 1
        assert len(data["jobs"]) >= 1
