import json
from dataclasses import replace

from fastapi.testclient import TestClient

from sentence_api.main import create_app
from sentence_api.config import Settings
from sentence_api.oss import VolcanoOSSUploader
from sentence_api.models import SentenceBoundary, SentenceBoundaryDocument
from sentence_api.processing import VideoProcessor
from sentence_api.repository import VideoRepository
from sentence_api.store import BoundaryStore


VIDEO_HASH = "a" * 64


def make_client(tmp_path):
    index_path = tmp_path / "boundaries.json"
    index_path.write_text(
        json.dumps({
            "videos": {
                VIDEO_HASH: {
                    "duration_ms": 9000,
                    "algorithm_version": "test-v1",
                    "sentences": [
                        {"index": 0, "start_ms": 0, "end_ms": 4000, "text": "One."},
                        {"index": 1, "start_ms": 4000, "end_ms": 9000, "text": "Two."},
                    ],
                }
            }
        }),
        encoding="utf-8",
    )
    settings = replace(
        Settings.from_env(),
        data_dir=tmp_path / "data",
        legacy_boundaries_file=index_path,
    )
    settings.ensure_directories()
    client = TestClient(
        create_app(
            BoundaryStore(index_path),
            settings=settings,
            repository=VideoRepository(settings.database_path),
        )
    )
    response = client.post(
        "/api/v1/auth/register",
        json={"username": "tester", "password": "password-123"},
    )
    assert response.status_code == 201, response.text
    client.headers.update({"Authorization": f"Bearer {response.json()['token']}"})
    return client


def test_lookup_returns_boundaries(tmp_path):
    client = make_client(tmp_path)

    response = client.get(f"/api/v1/videos/{VIDEO_HASH}/sentence-boundaries")

    assert response.status_code == 200
    assert response.json()["sentences"][1]["start_ms"] == 4000


def test_unknown_hash_returns_not_found(tmp_path):
    client = make_client(tmp_path)

    response = client.get(f"/api/v1/videos/{'b' * 64}/sentence-boundaries")

    assert response.status_code == 404
    assert response.json()["detail"]["code"] == "SENTENCE_BOUNDARIES_NOT_FOUND"


def test_invalid_hash_is_rejected(tmp_path):
    client = make_client(tmp_path)

    response = client.get("/api/v1/videos/not-a-sha256/sentence-boundaries")

    assert response.status_code == 422


def test_oss_video_url_is_used_for_catalog_and_content(tmp_path):
    index_path = tmp_path / "boundaries.json"
    index_path.write_text("{}", encoding="utf-8")
    settings = replace(
        Settings.from_env(),
        data_dir=tmp_path / "data",
        legacy_boundaries_file=index_path,
    )
    settings.ensure_directories()
    repository = VideoRepository(settings.database_path)
    repository.upsert_upload(
        video_hash=VIDEO_HASH,
        title="OSS lesson",
        filename="lesson.mp4",
        stored_filename=f"{VIDEO_HASH}.mp4",
        content_type="video/mp4",
        size_bytes=1,
        language=None,
    )
    remote_url = f"https://course-media.example.com/videos/{VIDEO_HASH}.mp4"
    repository.mark_oss_uploaded(VIDEO_HASH, remote_url)
    client = TestClient(
        create_app(
            BoundaryStore(index_path),
            settings=settings,
            repository=repository,
        )
    )

    catalog = client.get("/api/v1/videos")
    stream = client.get(f"/api/v1/videos/{VIDEO_HASH}/content", follow_redirects=False)

    assert catalog.status_code == 200
    assert catalog.json()["videos"][0]["stream_url"] == remote_url
    assert stream.status_code == 307
    assert stream.headers["location"] == remote_url


def test_volcano_oss_object_key_and_public_url(tmp_path):
    settings = replace(
        Settings.from_env(),
        volcano_oss_access_key="ak",
        volcano_oss_secret_key="sk",
        volcano_oss_bucket="course-media",
        volcano_oss_endpoint="tos-cn-beijing.volces.com",
        volcano_oss_region="cn-beijing",
        volcano_oss_key_prefix="lessons/videos/",
    )
    uploader = VolcanoOSSUploader(settings)
    key = uploader.object_key(VIDEO_HASH, f"{VIDEO_HASH}.mp4")

    assert settings.volcano_oss_enabled is True
    assert key == f"lessons/videos/{VIDEO_HASH}.mp4"
    assert uploader.public_url(key) == (
        f"https://course-media.tos-cn-beijing.volces.com/"
        f"lessons/videos/{VIDEO_HASH}.mp4"
    )


def test_processor_marks_ready_only_after_oss_upload(tmp_path, monkeypatch):
    settings = replace(Settings.from_env(), data_dir=tmp_path / "data")
    settings.ensure_directories()
    media_path = settings.videos_dir / f"{VIDEO_HASH}.mp4"
    media_path.write_bytes(b"video")
    repository = VideoRepository(settings.database_path)
    repository.upsert_upload(
        video_hash=VIDEO_HASH,
        title="OSS lesson",
        filename="lesson.mp4",
        stored_filename=f"{VIDEO_HASH}.mp4",
        content_type="video/mp4",
        size_bytes=5,
        language=None,
    )
    document = SentenceBoundaryDocument(
        video_hash=VIDEO_HASH,
        duration_ms=1000,
        algorithm_version="test",
        sentences=[SentenceBoundary(index=0, start_ms=0, end_ms=1000)],
    )
    uploads = []

    class EnabledUploader:
        enabled = True

        def upload(self, **kwargs):
            uploads.append(kwargs)
            return "https://media.example.com/lesson.mp4"

    class UnavailableTranscriber:
        available = False

    monkeypatch.setattr(
        "sentence_api.processing.make_entry",
        lambda *args, **kwargs: (
            {
                "duration_ms": 1000,
                "sentences": [{"index": 0, "start_ms": 0, "end_ms": 1000}],
            },
            {},
        ),
    )
    processor = VideoProcessor(
        settings,
        repository,
        UnavailableTranscriber(),
        EnabledUploader(),
    )

    processor.process(VIDEO_HASH)

    video = repository.get_video(VIDEO_HASH)
    assert uploads[0]["stored_filename"] == f"{VIDEO_HASH}.mp4"
    assert video["status"] == "ready"
    assert video["remote_url"] == "https://media.example.com/lesson.mp4"


def test_admin_can_split_sentence_and_reindexes_following_sentences(tmp_path):
    settings = replace(Settings.from_env(), data_dir=tmp_path / "data")
    settings.ensure_directories()
    repository = VideoRepository(settings.database_path)
    repository.upsert_upload(
        video_hash=VIDEO_HASH,
        title="Manual split lesson",
        filename="lesson.mp4",
        stored_filename=f"{VIDEO_HASH}.mp4",
        content_type="video/mp4",
        size_bytes=5,
        language="en",
    )
    repository.save_processing_result(
        SentenceBoundaryDocument(
            video_hash=VIDEO_HASH,
            duration_ms=12000,
            algorithm_version="test",
            sentences=[
                SentenceBoundary(
                    index=0,
                    start_ms=0,
                    end_ms=2000,
                    text="Before.",
                    language="en",
                    reference_speech_duration_ms=1800,
                ),
                SentenceBoundary(
                    index=1,
                    start_ms=2000,
                    end_ms=9000,
                    text="First part second part",
                    language="en",
                    reference_speech_duration_ms=6600,
                ),
                SentenceBoundary(
                    index=2,
                    start_ms=9000,
                    end_ms=12000,
                    text="Next sentence.",
                    language="en",
                    reference_speech_duration_ms=2800,
                ),
            ],
        ),
        None,
    )

    class UnavailableTranscriber:
        available = False

    client = TestClient(
        create_app(
            BoundaryStore(tmp_path / "boundaries.json"),
            settings=settings,
            repository=repository,
            transcriber=UnavailableTranscriber(),
        )
    )
    response = client.put(
        f"/api/v1/admin/videos/{VIDEO_HASH}/sentences/1/split",
        json={"text": "First part\nsecond part", "language": "en"},
    )

    assert response.status_code == 200
    replacements = response.json()
    assert [item["text"] for item in replacements] == ["First part", "second part"]
    assert replacements[0]["start_ms"] == 2000
    assert replacements[0]["end_ms"] == replacements[1]["start_ms"]
    assert replacements[1]["end_ms"] == 9000
    document = repository.get_document(VIDEO_HASH)
    assert [item.text for item in document.sentences] == [
        "Before.",
        "First part",
        "second part",
        "Next sentence.",
    ]
    assert [item.index for item in document.sentences] == [0, 1, 2, 3]


def test_admin_boundary_adjustment_updates_linked_next_start(tmp_path):
    settings = replace(Settings.from_env(), data_dir=tmp_path / "data")
    settings.ensure_directories()
    repository = VideoRepository(settings.database_path)
    repository.upsert_upload(
        video_hash=VIDEO_HASH,
        title="Boundary lesson",
        filename="lesson.mp4",
        stored_filename=f"{VIDEO_HASH}.mp4",
        content_type="video/mp4",
        size_bytes=5,
        language="en",
    )
    repository.save_processing_result(
        SentenceBoundaryDocument(
            video_hash=VIDEO_HASH,
            duration_ms=9000,
            algorithm_version="test",
            sentences=[
                SentenceBoundary(
                    index=0,
                    start_ms=0,
                    end_ms=4000,
                    text="First.",
                    language="en",
                    reference_speech_duration_ms=3600,
                ),
                SentenceBoundary(
                    index=1,
                    start_ms=4000,
                    end_ms=9000,
                    text="Second.",
                    language="en",
                    reference_speech_duration_ms=4600,
                ),
            ],
        ),
        None,
    )

    class UnavailableTranscriber:
        available = False

    client = TestClient(
        create_app(
            BoundaryStore(tmp_path / "boundaries.json"),
            settings=settings,
            repository=repository,
            transcriber=UnavailableTranscriber(),
        )
    )
    response = client.put(
        f"/api/v1/admin/videos/{VIDEO_HASH}/sentences/0/boundary",
        json={"delta_ms": 250},
    )

    assert response.status_code == 200
    updated = response.json()
    assert [item["index"] for item in updated] == [0, 1]
    assert updated[0]["end_ms"] == 4250
    assert updated[1]["start_ms"] == 4250
    assert updated[0]["reference_speech_duration_ms"] == 3850
    assert updated[1]["reference_speech_duration_ms"] == 4350

    document = repository.get_document(VIDEO_HASH)
    assert [item.end_ms for item in document.sentences] == [4250, 9000]
    assert [item.start_ms for item in document.sentences] == [0, 4250]
