from __future__ import annotations

from datetime import UTC
from datetime import datetime
from pathlib import Path
from uuid import uuid4

import pytest

from transcription.db.models import Document
from transcription.db.models import Job
from transcription.db.models import DocumentPerson
from transcription.db.models import DocumentPersonRole
from transcription.db.models import Person
from transcription.db.models import Source
from transcription.services.documents import DocumentDeleteBlockedError
from transcription.services.documents import DocumentError
from transcription.services.documents import DocumentService


@pytest.mark.asyncio
async def test_read_document_detail_allows_missing_sources(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    created = await service.create_document(
        Document(
            id=uuid4(),
            name="detail-doc",
            document_type="letter",
        )
    )

    detail = await service.read_document_detail(created.id)

    assert detail.id == created.id
    assert detail.sources == []


@pytest.mark.asyncio
async def test_update_document_refreshes_updated_timestamp(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    created = await service.create_document(
        Document(
            id=uuid4(),
            name="timestamp-doc",
            document_type="letter",
            updated_at=datetime(2000, 1, 1, tzinfo=UTC),
        )
    )

    original_updated_at = created.updated_at
    created.notes = "updated"

    updated = await service.update_document(created)

    assert updated.notes == "updated"
    assert updated.updated_at >= original_updated_at


@pytest.mark.asyncio
async def test_delete_document_blocks_when_dependencies_exist(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    document = await service.create_document(
        Document(
            id=uuid4(),
            name="blocked-delete",
            document_type="record",
        )
    )

    async with service._session_scope() as session:
        session.add(
            Source(
                document_id=document.id,
                page_number=1,
                upload_name="001_page.png",
                filename="001_page.png",
                file_path="uploads/001_page.png",
            )
        )
        session.add(Job(document_id=document.id))
        await session.commit()

    with pytest.raises(DocumentDeleteBlockedError):
        await service.delete_document(document)


@pytest.mark.asyncio
async def test_delete_document_succeeds_when_unlinked(default_session_factory, tmp_path):
    service = DocumentService(session_factory=default_session_factory)
    service.settings.upload_dir = tmp_path

    document = await service.create_document(
        Document(
            id=uuid4(),
            name="free-delete",
            document_type="memo",
        )
    )

    document_dir = service.settings.upload_dir / "documents" / str(document.id)
    document_dir.mkdir(parents=True, exist_ok=True)
    (document_dir / "leftover.txt").write_text("orphan", encoding="utf-8")

    await service.delete_document(document)

    assert not document_dir.exists()

    with pytest.raises(DocumentError):
        await service.read_document_detail(document.id)


@pytest.mark.asyncio
async def test_delete_document_removes_populated_storage_tree(default_session_factory, tmp_path):
    service = DocumentService(session_factory=default_session_factory)
    service.settings.upload_dir = tmp_path

    document = await service.create_document(
        Document(
            id=uuid4(),
            name="tree-delete",
            document_type="memo",
        )
    )

    document_dir = service.settings.upload_dir / "documents" / str(document.id)
    (document_dir / "page-1.jpg").parent.mkdir(parents=True, exist_ok=True)
    (document_dir / "page-1.jpg").write_bytes(b"one")
    (document_dir / "page-2.jpg").write_bytes(b"two")
    (document_dir / "nested" / "manifest.json").parent.mkdir(parents=True, exist_ok=True)
    (document_dir / "nested" / "manifest.json").write_text('{"ok": true}', encoding="utf-8")

    assert document_dir.exists()

    await service.delete_document(document)

    assert not document_dir.exists()


@pytest.mark.asyncio
async def test_read_person_detail_loads_document_links(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    document = await service.create_document(
        Document(
            id=uuid4(),
            name="linked-doc",
            document_type="letter",
        )
    )
    person = await service.create_person(Person(full_name="Linked Person"))
    await service.create_document_person(
        DocumentPerson(
            document_id=document.id,
            person_id=person.id,
            role=DocumentPersonRole.AUTHOR,
        )
    )

    detail = await service.read_person_detail(person.id)

    assert detail.id == person.id
    assert len(detail.document_people) == 1
    assert detail.document_people[0].document is not None
    assert detail.document_people[0].document.name == "linked-doc"


@pytest.mark.asyncio
async def test_update_person_refreshes_updated_timestamp(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    created = await service.create_person(
        Person(
            full_name="timestamp-person",
            updated_at=datetime(2000, 1, 1, tzinfo=UTC),
        )
    )
    original_updated_at = created.updated_at
    created.display_name = "updated"

    updated = await service.update_person(created)

    assert updated.display_name == "updated"
    assert updated.updated_at >= original_updated_at


@pytest.mark.asyncio
async def test_delete_person_removes_links_when_linked_documents_exist(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    document = await service.create_document(
        Document(
            id=uuid4(),
            name="block-person-delete-doc",
            document_type="record",
        )
    )
    person = await service.create_person(Person(full_name="Blocked Person"))
    await service.create_document_person(
        DocumentPerson(
            document_id=document.id,
            person_id=person.id,
            role=DocumentPersonRole.AUTHOR,
        )
    )

    await service.delete_person(person)

    links = await service.list_document_people(person_id=person.id)
    assert links == []

    with pytest.raises(DocumentError):
        await service.read_person_detail(person.id)


@pytest.mark.asyncio
async def test_delete_person_succeeds_when_unlinked(default_session_factory):
    service = DocumentService(session_factory=default_session_factory)

    person = await service.create_person(Person(full_name="Free Person"))

    await service.delete_person(person)

    with pytest.raises(DocumentError):
        await service.read_person_detail(person.id)
