"""
Tests for ReceiptAutoCreateService (US-009).

US-009 Acceptance Criteria:
- Clasa ReceiptAutoCreateService cu metoda create_from_ocr_result(job_id, ocr_result, user, batch_id)
- Mapare completă OCR fields → Receipt fields (vezi ExtractionData schema)
- Creare attachment cu fișierul original din job.file_path
- Generare accounting entries via AccountingService.generate_entries() existent
- Validare minimă: suma > 0, dată validă (nu în viitor)
- Return ReceiptCreateResult cu receipt_id sau error_message
- Actualizează batch_jobs cu receipt_id după creare
- pytest tests pass
"""

import pytest
from datetime import date, timedelta
from decimal import Decimal
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import tempfile
import os

from backend.modules.data_entry.services.receipt_auto_create import (
    ReceiptAutoCreateService,
    ReceiptCreateResult,
)
from backend.modules.data_entry.schemas.ocr import ExtractionData, TvaEntry, PaymentMethod
from backend.modules.data_entry.db.models.receipt import ReceiptType, ReceiptDirection


# ============================================================================
# Fixtures
# ============================================================================

@pytest.fixture
def valid_ocr_result():
    """Create a valid OCR extraction result."""
    return ExtractionData(
        receipt_type="bon_fiscal",
        receipt_number="123456",
        receipt_series="ABC",
        receipt_date=date.today() - timedelta(days=1),  # Yesterday
        amount=Decimal("125.50"),
        partner_name="MEGA IMAGE SRL",
        cui="12345678",
        description="Produse alimentare",
        tva_entries=[
            TvaEntry(code="A", percent=19, amount=Decimal("20.00")),
            TvaEntry(code="B", percent=9, amount=Decimal("5.50")),
        ],
        tva_total=Decimal("25.50"),
        address="Str. Test Nr. 1, Bucuresti",
        items_count=5,
        payment_methods=[
            PaymentMethod(method="CARD", amount=Decimal("125.50")),
        ],
        suggested_payment_mode="banca",
        confidence_amount=0.95,
        confidence_date=0.90,
        confidence_vendor=0.85,
        overall_confidence=0.90,
        raw_text="BON FISCAL\nMEGA IMAGE SRL\nTotal: 125.50 LEI",
    )


@pytest.fixture
def minimal_ocr_result():
    """Create minimal valid OCR result with only required fields."""
    return ExtractionData(
        receipt_date=date.today(),
        amount=Decimal("50.00"),
    )


@pytest.fixture
def mock_session():
    """Create a mock async session."""
    session = AsyncMock()
    session.add = MagicMock()
    session.flush = AsyncMock()
    session.commit = AsyncMock()
    session.rollback = AsyncMock()
    session.execute = AsyncMock()
    return session


@pytest.fixture
def temp_file():
    """Create a temporary file for attachment testing."""
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as f:
        f.write(b"fake pdf content for testing")
        temp_path = f.name
    yield temp_path
    # Cleanup
    if os.path.exists(temp_path):
        os.remove(temp_path)


# ============================================================================
# Unit Tests - Validation
# ============================================================================

class TestValidation:
    """Test OCR result validation."""

    def test_validate_valid_result(self, valid_ocr_result):
        """Test validation passes for valid OCR result."""
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(valid_ocr_result)
        assert is_valid is True
        assert error == ""

    def test_validate_missing_amount(self):
        """Test validation fails when amount is missing."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=None,
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is False
        assert "amount" in error.lower()

    def test_validate_zero_amount(self):
        """Test validation fails when amount is zero."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("0"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is False
        assert "amount" in error.lower()

    def test_validate_negative_amount(self):
        """Test validation fails when amount is negative."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("-10.00"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is False
        assert "amount" in error.lower()

    def test_validate_missing_date(self):
        """Test validation fails when date is missing."""
        ocr_result = ExtractionData(
            receipt_date=None,
            amount=Decimal("100.00"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is False
        assert "date" in error.lower()

    def test_validate_future_date(self):
        """Test validation fails when date is in the future."""
        ocr_result = ExtractionData(
            receipt_date=date.today() + timedelta(days=1),  # Tomorrow
            amount=Decimal("100.00"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is False
        assert "future" in error.lower()

    def test_validate_today_date_passes(self):
        """Test validation passes when date is today."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("100.00"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is True
        assert error == ""


# ============================================================================
# Unit Tests - Field Mapping
# ============================================================================

class TestFieldMapping:
    """Test OCR to Receipt field mapping."""

    def test_map_basic_fields(self, valid_ocr_result):
        """Test basic field mapping from OCR to Receipt."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )

        assert receipt_data.receipt_number == "123456"
        assert receipt_data.receipt_series == "ABC"
        assert receipt_data.receipt_date == valid_ocr_result.receipt_date
        assert receipt_data.amount == Decimal("125.50")
        assert receipt_data.partner_name == "MEGA IMAGE SRL"
        assert receipt_data.cui == "12345678"
        assert receipt_data.description == "Produse alimentare"
        assert receipt_data.company_id == 1

    def test_map_receipt_type_bon_fiscal(self, valid_ocr_result):
        """Test mapping bon_fiscal type correctly."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )
        assert receipt_data.receipt_type == ReceiptType.BON_FISCAL

    def test_map_receipt_type_chitanta(self, valid_ocr_result):
        """Test mapping chitanta type correctly."""
        valid_ocr_result.receipt_type = "chitanta"
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )
        assert receipt_data.receipt_type == ReceiptType.CHITANTA

    def test_map_default_direction_is_expense(self, valid_ocr_result):
        """Test default direction is expense (cheltuiala)."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )
        assert receipt_data.direction == ReceiptDirection.CHELTUIALA

    def test_map_tva_breakdown(self, valid_ocr_result):
        """Test TVA breakdown mapping."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )

        assert receipt_data.tva_breakdown is not None
        assert len(receipt_data.tva_breakdown) == 2
        assert receipt_data.tva_breakdown[0].code == "A"
        assert receipt_data.tva_breakdown[0].percent == 19
        assert receipt_data.tva_breakdown[0].amount == Decimal("20.00")
        assert receipt_data.tva_total == Decimal("25.50")

    def test_map_payment_methods(self, valid_ocr_result):
        """Test payment methods mapping."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )

        assert receipt_data.payment_methods is not None
        assert len(receipt_data.payment_methods) == 1
        assert receipt_data.payment_methods[0].method == "CARD"
        assert receipt_data.payment_methods[0].amount == Decimal("125.50")
        assert receipt_data.payment_mode == "banca"

    def test_map_vendor_address(self, valid_ocr_result):
        """Test vendor address mapping."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )
        assert receipt_data.vendor_address == "Str. Test Nr. 1, Bucuresti"

    def test_map_items_count(self, valid_ocr_result):
        """Test items count mapping."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )
        assert receipt_data.items_count == 5

    def test_map_raw_text_truncated(self, valid_ocr_result):
        """Test raw text is truncated to 5000 chars."""
        valid_ocr_result.raw_text = "X" * 10000
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=valid_ocr_result,
            company_id=1,
        )
        assert len(receipt_data.ocr_raw_text) == 5000

    def test_map_minimal_result(self, minimal_ocr_result):
        """Test mapping works with minimal required fields."""
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=minimal_ocr_result,
            company_id=1,
        )

        assert receipt_data.amount == Decimal("50.00")
        assert receipt_data.receipt_date == date.today()
        assert receipt_data.company_id == 1
        assert receipt_data.receipt_number is None
        assert receipt_data.partner_name is None
        assert receipt_data.tva_breakdown is None


# ============================================================================
# Unit Tests - ReceiptCreateResult
# ============================================================================

class TestReceiptCreateResult:
    """Test ReceiptCreateResult dataclass."""

    def test_success_result(self):
        """Test successful result has receipt_id."""
        result = ReceiptCreateResult(success=True, receipt_id=42)
        assert result.success is True
        assert result.receipt_id == 42
        assert result.error_message is None

    def test_failure_result(self):
        """Test failure result has error_message."""
        result = ReceiptCreateResult(success=False, error_message="Validation failed")
        assert result.success is False
        assert result.receipt_id is None
        assert result.error_message == "Validation failed"


# ============================================================================
# Integration Tests with Mocked Dependencies
# ============================================================================

class TestCreateFromOcrResult:
    """Test create_from_ocr_result method."""

    @pytest.mark.asyncio
    async def test_successful_creation(self, mock_session, valid_ocr_result, temp_file):
        """Test successful receipt creation from OCR result."""
        # Mock ReceiptCRUD.create to return a mock receipt
        mock_receipt = MagicMock()
        mock_receipt.id = 1
        mock_receipt.amount = valid_ocr_result.amount
        mock_receipt.partner_name = valid_ocr_result.partner_name

        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.AccountingEntryCRUD"
        ) as mock_entry_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.settings"
        ) as mock_settings:

            mock_crud.create = AsyncMock(return_value=mock_receipt)
            mock_entry_crud.create_bulk = AsyncMock()

            # Mock settings for attachment path
            mock_settings.data_entry_upload_path_resolved = Path(tempfile.gettempdir())

            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="test-job-123",
                ocr_result=valid_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
                file_path=temp_file,
                original_filename="bon.pdf",
            )

            assert result.success is True
            assert result.receipt_id == 1
            assert result.error_message is None
            mock_crud.create.assert_called_once()
            mock_session.commit.assert_called_once()

    @pytest.mark.asyncio
    async def test_validation_failure_returns_error(self, mock_session):
        """Test that validation failure returns error result."""
        invalid_ocr = ExtractionData(
            receipt_date=date.today() + timedelta(days=5),  # Future date
            amount=Decimal("100.00"),
        )

        result = await ReceiptAutoCreateService.create_from_ocr_result(
            session=mock_session,
            job_id="test-job-123",
            ocr_result=invalid_ocr,
            username="test_user",
            batch_id=1,
            company_id=1,
        )

        assert result.success is False
        assert result.error_message is not None
        assert "future" in result.error_message.lower()
        mock_session.commit.assert_not_called()

    @pytest.mark.asyncio
    async def test_zero_amount_returns_error(self, mock_session):
        """Test that zero amount returns error result."""
        zero_amount_ocr = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("0"),
        )

        result = await ReceiptAutoCreateService.create_from_ocr_result(
            session=mock_session,
            job_id="test-job-123",
            ocr_result=zero_amount_ocr,
            username="test_user",
            batch_id=1,
            company_id=1,
        )

        assert result.success is False
        assert "amount" in result.error_message.lower()

    @pytest.mark.asyncio
    async def test_batch_job_updated_with_receipt_id(self, mock_session, valid_ocr_result):
        """Test that batch_jobs table is updated with receipt_id."""
        mock_receipt = MagicMock()
        mock_receipt.id = 42

        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.AccountingEntryCRUD"
        ) as mock_entry_crud:

            mock_crud.create = AsyncMock(return_value=mock_receipt)
            mock_entry_crud.create_bulk = AsyncMock()

            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="test-job-456",
                ocr_result=valid_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
            )

            assert result.success is True
            # Verify execute was called (for update batch_jobs)
            mock_session.execute.assert_called()

    @pytest.mark.asyncio
    async def test_accounting_entries_generated(self, mock_session, valid_ocr_result):
        """Test that accounting entries are generated for the receipt."""
        mock_receipt = MagicMock()
        mock_receipt.id = 1
        mock_receipt.amount = valid_ocr_result.amount
        mock_receipt.direction = ReceiptDirection.CHELTUIALA
        mock_receipt.expense_type_code = None
        mock_receipt.payment_mode = "banca"
        mock_receipt.cash_register_account = None

        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.AccountingEntryCRUD"
        ) as mock_entry_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptService"
        ) as mock_service:

            mock_crud.create = AsyncMock(return_value=mock_receipt)
            mock_entry_crud.create_bulk = AsyncMock()
            mock_service.generate_accounting_entries = MagicMock(return_value=[
                MagicMock(),  # Mock entries
            ])

            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="test-job-789",
                ocr_result=valid_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
            )

            assert result.success is True
            mock_service.generate_accounting_entries.assert_called_once_with(mock_receipt)
            mock_entry_crud.create_bulk.assert_called_once()

    @pytest.mark.asyncio
    async def test_exception_triggers_rollback(self, mock_session, valid_ocr_result):
        """Test that exception during creation triggers rollback."""
        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud:

            mock_crud.create = AsyncMock(side_effect=Exception("Database error"))

            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="test-job-err",
                ocr_result=valid_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
            )

            assert result.success is False
            assert "Database error" in result.error_message
            mock_session.rollback.assert_called_once()

    @pytest.mark.asyncio
    async def test_attachment_creation_failure_doesnt_fail_receipt(
        self, mock_session, valid_ocr_result
    ):
        """Test that attachment creation failure doesn't fail receipt creation."""
        mock_receipt = MagicMock()
        mock_receipt.id = 1

        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.AccountingEntryCRUD"
        ) as mock_entry_crud:

            mock_crud.create = AsyncMock(return_value=mock_receipt)
            mock_entry_crud.create_bulk = AsyncMock()

            # Pass a non-existent file path
            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="test-job-attach",
                ocr_result=valid_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
                file_path="/nonexistent/path/file.pdf",
            )

            # Receipt should still be created successfully
            assert result.success is True
            assert result.receipt_id == 1

    @pytest.mark.asyncio
    async def test_entry_generation_failure_doesnt_fail_receipt(
        self, mock_session, valid_ocr_result
    ):
        """Test that entry generation failure doesn't fail receipt creation."""
        mock_receipt = MagicMock()
        mock_receipt.id = 1

        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptService"
        ) as mock_service:

            mock_crud.create = AsyncMock(return_value=mock_receipt)
            mock_service.generate_accounting_entries = MagicMock(
                side_effect=Exception("Entry generation failed")
            )

            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="test-job-entry-err",
                ocr_result=valid_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
            )

            # Receipt should still be created successfully
            assert result.success is True
            assert result.receipt_id == 1


# ============================================================================
# Edge Cases
# ============================================================================

class TestEdgeCases:
    """Test edge cases and boundary conditions."""

    @pytest.mark.asyncio
    async def test_minimal_valid_ocr_creates_receipt(self, mock_session, minimal_ocr_result):
        """Test that minimal valid OCR result creates receipt."""
        mock_receipt = MagicMock()
        mock_receipt.id = 1

        with patch(
            "backend.modules.data_entry.services.receipt_auto_create.ReceiptCRUD"
        ) as mock_crud, patch(
            "backend.modules.data_entry.services.receipt_auto_create.AccountingEntryCRUD"
        ):
            mock_crud.create = AsyncMock(return_value=mock_receipt)

            result = await ReceiptAutoCreateService.create_from_ocr_result(
                session=mock_session,
                job_id="minimal-job",
                ocr_result=minimal_ocr_result,
                username="test_user",
                batch_id=1,
                company_id=1,
            )

            assert result.success is True

    def test_empty_tva_entries_maps_to_none(self):
        """Test that empty TVA entries list maps to None."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("100.00"),
            tva_entries=[],
        )
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=ocr_result,
            company_id=1,
        )
        assert receipt_data.tva_breakdown is None

    def test_empty_payment_methods_maps_to_none(self):
        """Test that empty payment methods list maps to None."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("100.00"),
            payment_methods=[],
        )
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=ocr_result,
            company_id=1,
        )
        assert receipt_data.payment_methods is None

    def test_none_raw_text_handled(self):
        """Test that None raw text is handled correctly."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("100.00"),
            raw_text="",
        )
        receipt_data = ReceiptAutoCreateService._map_ocr_to_receipt(
            ocr_result=ocr_result,
            company_id=1,
        )
        assert receipt_data.ocr_raw_text is None

    def test_very_small_amount_valid(self):
        """Test that very small positive amount is valid."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("0.01"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is True

    def test_very_large_amount_valid(self):
        """Test that very large amount is valid."""
        ocr_result = ExtractionData(
            receipt_date=date.today(),
            amount=Decimal("999999999.99"),
        )
        is_valid, error = ReceiptAutoCreateService._validate_ocr_result(ocr_result)
        assert is_valid is True


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
