"""Unit tests for symbol upload API endpoints."""

from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4

import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient

from src.api.schemas.symbols import (
    SymbolDeleteResponse,
    SymbolEntry,
    SymbolListResponse,
    SymbolUploadRequest,
    SymbolUploadResponse,
)
from src.services.symbol_service import (
    CUSTOM_SYMBOL_CONTEXT,
    SymbolDeleteResult,
    SymbolService,
    SymbolUploadResult,
)


class TestSymbolUploadRequestSchema:
    """Tests for SymbolUploadRequest schema validation."""

    def test_valid_request(self):
        """Test valid symbol upload request."""
        request = SymbolUploadRequest(
            symbols=[
                SymbolEntry(symbol="○", meaning="Yes/Applicable"),
                SymbolEntry(symbol="×", meaning="No/Not applicable"),
            ]
        )
        assert len(request.symbols) == 2

    def test_empty_symbols_rejected(self):
        """Test that empty symbols array is rejected."""
        with pytest.raises(ValueError):
            SymbolUploadRequest(symbols=[])

    def test_duplicate_symbols_rejected(self):
        """Test that duplicate symbols are rejected."""
        with pytest.raises(ValueError, match="Duplicate symbol"):
            SymbolUploadRequest(
                symbols=[
                    SymbolEntry(symbol="○", meaning="First meaning"),
                    SymbolEntry(symbol="○", meaning="Second meaning"),
                ]
            )

    def test_symbol_too_long_rejected(self):
        """Test that symbol exceeding max length is rejected."""
        with pytest.raises(ValueError):
            SymbolEntry(symbol="x" * 51, meaning="Too long")

    def test_meaning_too_long_rejected(self):
        """Test that meaning exceeding max length is rejected."""
        with pytest.raises(ValueError):
            SymbolEntry(symbol="○", meaning="x" * 501)

    def test_empty_symbol_rejected(self):
        """Test that empty symbol is rejected."""
        with pytest.raises(ValueError):
            SymbolEntry(symbol="", meaning="Some meaning")

    def test_empty_meaning_rejected(self):
        """Test that empty meaning is rejected."""
        with pytest.raises(ValueError):
            SymbolEntry(symbol="○", meaning="")

    def test_max_symbols_allowed(self):
        """Test that up to 100 symbols are allowed."""
        symbols = [
            SymbolEntry(symbol=f"S{i}", meaning=f"Meaning {i}") for i in range(100)
        ]
        request = SymbolUploadRequest(symbols=symbols)
        assert len(request.symbols) == 100

    def test_over_max_symbols_rejected(self):
        """Test that over 100 symbols is rejected."""
        symbols = [
            SymbolEntry(symbol=f"S{i}", meaning=f"Meaning {i}") for i in range(101)
        ]
        with pytest.raises(ValueError):
            SymbolUploadRequest(symbols=symbols)


class TestSymbolService:
    """Tests for SymbolService class."""

    @pytest.mark.asyncio
    async def test_upload_custom_symbols_adds_new(self):
        """Test uploading new custom symbols."""
        service = SymbolService()
        doc_id = uuid4()

        mock_session = MagicMock()
        mock_session.execute = AsyncMock(return_value=MagicMock(scalar_one_or_none=lambda: None))
        mock_session.add = MagicMock()
        mock_session.commit = AsyncMock()

        result = await service.upload_custom_symbols(
            document_id=doc_id,
            symbols=[{"symbol": "★", "meaning": "Priority"}],
            session=mock_session,
        )

        assert result.document_id == doc_id
        assert result.symbols_added == 1
        assert result.symbols_updated == 0

    @pytest.mark.asyncio
    async def test_check_document_needs_reprocessing_completed(self):
        """Test that completed documents need reprocessing."""
        service = SymbolService()
        doc_id = uuid4()

        mock_session = MagicMock()
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = "completed"
        mock_session.execute = AsyncMock(return_value=mock_result)

        needs_reprocessing = await service.check_document_needs_reprocessing(
            doc_id, mock_session
        )

        assert needs_reprocessing is True

    @pytest.mark.asyncio
    async def test_check_document_needs_reprocessing_pending(self):
        """Test that pending documents don't need reprocessing."""
        service = SymbolService()
        doc_id = uuid4()

        mock_session = MagicMock()
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = "pending"
        mock_session.execute = AsyncMock(return_value=mock_result)

        needs_reprocessing = await service.check_document_needs_reprocessing(
            doc_id, mock_session
        )

        assert needs_reprocessing is False


class TestSymbolUploadResultDataclass:
    """Tests for SymbolUploadResult dataclass."""

    def test_to_dict(self):
        """Test SymbolUploadResult.to_dict() method."""
        doc_id = uuid4()
        result = SymbolUploadResult(
            document_id=doc_id,
            symbols_added=2,
            symbols_updated=1,
            reprocessing_triggered=True,
        )

        d = result.to_dict()
        assert d["document_id"] == str(doc_id)
        assert d["symbols_added"] == 2
        assert d["symbols_updated"] == 1
        assert d["reprocessing_triggered"] is True


class TestSymbolDeleteResultDataclass:
    """Tests for SymbolDeleteResult dataclass."""

    def test_to_dict(self):
        """Test SymbolDeleteResult.to_dict() method."""
        doc_id = uuid4()
        result = SymbolDeleteResult(
            document_id=doc_id,
            symbol="★",
            deleted=True,
            reprocessing_triggered=False,
        )

        d = result.to_dict()
        assert d["document_id"] == str(doc_id)
        assert d["symbol"] == "★"
        assert d["deleted"] is True
        assert d["reprocessing_triggered"] is False
