"""Business logic service for receipts workflow."""

from decimal import Decimal, ROUND_HALF_UP
from typing import List, Optional, Tuple

from sqlalchemy.ext.asyncio import AsyncSession

from backend.modules.data_entry.db.models.receipt import Receipt, ReceiptStatus, ReceiptDirection
from backend.modules.data_entry.db.models.accounting_entry import EntryType
from backend.modules.data_entry.db.crud.receipt import ReceiptCRUD
from backend.modules.data_entry.db.crud.accounting_entry import AccountingEntryCRUD
from backend.modules.data_entry.schemas.receipt import (
    ReceiptCreate,
    ReceiptUpdate,
    ReceiptFilter,
    ReceiptResponse,
    ReceiptListResponse,
    ProcessingStats,
    AccountingEntryCreate,
)
from backend.modules.data_entry.services.expense_types import EXPENSE_TYPES, get_expense_type


# Payment mode to accounting account mapping
PAYMENT_MODE_ACCOUNTS = {
    'casa': ('5311', 'Casa in lei'),
    'banca': ('5121', 'Conturi la banci in lei'),
    'avans_decontare': ('542', 'Avansuri de trezorerie'),
}


class ReceiptService:
    """Service for receipt business logic and workflow."""

    @staticmethod
    async def create_receipt(
        session: AsyncSession,
        data: ReceiptCreate,
        created_by: str,
    ) -> Receipt:
        """Create a new receipt in DRAFT status."""
        return await ReceiptCRUD.create(session, data, created_by)

    @staticmethod
    async def get_receipt(
        session: AsyncSession,
        receipt_id: int,
    ) -> Optional[Receipt]:
        """Get receipt by ID with all relationships."""
        return await ReceiptCRUD.get_by_id(session, receipt_id, include_relations=True)

    @staticmethod
    async def get_receipts(
        session: AsyncSession,
        filters: ReceiptFilter,
    ) -> ReceiptListResponse:
        """Get paginated list of receipts with processing_stats (US-012)."""
        receipts, total = await ReceiptCRUD.get_list(session, filters)

        pages = (total + filters.page_size - 1) // filters.page_size if total > 0 else 1

        # Get processing stats for bulk uploaded receipts (US-012)
        stats_dict = await ReceiptCRUD.get_processing_stats(
            session,
            company_id=filters.company_id,
            batch_id=filters.batch_id,
        )
        processing_stats = ProcessingStats(**stats_dict)

        return ReceiptListResponse(
            items=[ReceiptResponse.model_validate(r) for r in receipts],
            total=total,
            page=filters.page,
            page_size=filters.page_size,
            pages=pages,
            processing_stats=processing_stats,
        )

    @staticmethod
    async def update_receipt(
        session: AsyncSession,
        receipt_id: int,
        data: ReceiptUpdate,
        username: str,
    ) -> Tuple[bool, str, Optional[Receipt]]:
        """
        Update receipt (only DRAFT status).
        Returns (success, message, receipt).
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", None

        if not await ReceiptCRUD.can_edit(receipt, username):
            return False, "Cannot edit this receipt", None

        updated = await ReceiptCRUD.update(session, receipt, data)
        return True, "Receipt updated", updated

    @staticmethod
    async def delete_receipt(
        session: AsyncSession,
        receipt_id: int,
        username: str,
    ) -> Tuple[bool, str]:
        """
        Delete receipt (only DRAFT status).
        Returns (success, message).
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found"

        if not await ReceiptCRUD.can_delete(receipt, username):
            return False, "Cannot delete this receipt"

        await ReceiptCRUD.delete(session, receipt)
        return True, "Receipt deleted"

    @staticmethod
    def generate_accounting_entries(receipt: Receipt) -> List[AccountingEntryCreate]:
        """
        Generate accounting entries based on receipt data and expense type.
        """
        entries: List[AccountingEntryCreate] = []

        # Get expense type configuration
        expense_type = get_expense_type(receipt.expense_type_code or "OTHER")
        if not expense_type:
            expense_type = EXPENSE_TYPES["OTHER"]

        amount = Decimal(str(receipt.amount))

        if receipt.direction == ReceiptDirection.CHELTUIALA:
            # Expense: Debit expense account, Credit cash/bank
            if expense_type.has_vat:
                # Calculate net and VAT
                vat_rate = expense_type.vat_percent / Decimal("100")
                net_amount = (amount / (1 + vat_rate)).quantize(
                    Decimal("0.01"), rounding=ROUND_HALF_UP
                )
                vat_amount = amount - net_amount

                # Debit: Expense account (net)
                entries.append(AccountingEntryCreate(
                    entry_type=EntryType.DEBIT,
                    account_code=expense_type.account_code,
                    account_name=expense_type.account_name,
                    amount=net_amount,
                ))

                # Debit: VAT deductible
                entries.append(AccountingEntryCreate(
                    entry_type=EntryType.DEBIT,
                    account_code=expense_type.vat_account,
                    account_name="TVA deductibila",
                    amount=vat_amount,
                ))
            else:
                # No VAT - full amount to expense
                entries.append(AccountingEntryCreate(
                    entry_type=EntryType.DEBIT,
                    account_code=expense_type.account_code,
                    account_name=expense_type.account_name,
                    amount=amount,
                ))

            # Credit entry - based on payment_mode (new) or cash_register (legacy)
            if receipt.payment_mode and receipt.payment_mode in PAYMENT_MODE_ACCOUNTS:
                credit_account, credit_name = PAYMENT_MODE_ACCOUNTS[receipt.payment_mode]
            elif receipt.cash_register_account:
                # Backwards compatibility for existing receipts
                credit_account = receipt.cash_register_account
                credit_name = receipt.cash_register_name or "Casa/Banca"
            else:
                # Default fallback
                credit_account = "5311"
                credit_name = "Casa in lei"

            entries.append(AccountingEntryCreate(
                entry_type=EntryType.CREDIT,
                account_code=credit_account,
                account_name=credit_name,
                amount=amount,
            ))

        else:
            # Income: Debit cash/bank, Credit income account
            # Based on payment_mode (new) or cash_register (legacy)
            if receipt.payment_mode and receipt.payment_mode in PAYMENT_MODE_ACCOUNTS:
                cash_account, cash_name = PAYMENT_MODE_ACCOUNTS[receipt.payment_mode]
            elif receipt.cash_register_account:
                cash_account = receipt.cash_register_account
                cash_name = receipt.cash_register_name or "Casa/Banca"
            else:
                cash_account = "5311"
                cash_name = "Casa in lei"

            # Debit: Cash/Bank
            entries.append(AccountingEntryCreate(
                entry_type=EntryType.DEBIT,
                account_code=cash_account,
                account_name=cash_name,
                amount=amount,
            ))

            # Credit: Income account (7xx - to be configured)
            entries.append(AccountingEntryCreate(
                entry_type=EntryType.CREDIT,
                account_code="7588",
                account_name="Alte venituri din exploatare",
                amount=amount,
            ))

        return entries

    @staticmethod
    async def submit_for_review(
        session: AsyncSession,
        receipt_id: int,
        username: str,
    ) -> Tuple[bool, str, Optional[Receipt]]:
        """
        Submit receipt for review (DRAFT/REJECTED → PENDING_REVIEW).
        Generates accounting entries automatically.
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", None

        if not await ReceiptCRUD.can_submit(receipt, username):
            return False, "Cannot submit this receipt", None

        # Check if receipt has at least one attachment
        if not receipt.attachments:
            return False, "Receipt must have at least one attachment", None

        # Check required fields
        if not receipt.expense_type_code:
            return False, "Expense type is required", None

        # Validate payment_mode or cash_register (backwards compatibility)
        if not receipt.payment_mode and not receipt.cash_register_account:
            return False, "Modul de plata este obligatoriu", None

        # Generate accounting entries
        entries = ReceiptService.generate_accounting_entries(receipt)

        # Delete existing entries and create new ones
        await AccountingEntryCRUD.delete_all_for_receipt(session, receipt_id)
        await AccountingEntryCRUD.create_bulk(session, receipt_id, entries, is_auto_generated=True)

        # Refresh receipt to clear stale relationship references after entry deletion
        await session.refresh(receipt)

        # Update status
        updated = await ReceiptCRUD.update_status(
            session, receipt, ReceiptStatus.PENDING_REVIEW
        )

        # Reload with entries
        updated = await ReceiptCRUD.get_by_id(session, receipt_id)

        return True, "Receipt submitted for review", updated

    @staticmethod
    async def approve_receipt(
        session: AsyncSession,
        receipt_id: int,
        username: str,
    ) -> Tuple[bool, str, Optional[Receipt]]:
        """
        Approve receipt (PENDING_REVIEW → APPROVED).
        Requires valid CUI (fiscal code) for approval.
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", None

        if receipt.status != ReceiptStatus.PENDING_REVIEW:
            return False, "Receipt is not pending review", None

        # Validate CUI is present (required for Oracle import)
        if not receipt.cui:
            return False, "Trebuie completat codul fiscal (CUI) pentru aprobare", None

        # Validate accounting entries
        if not receipt.entries:
            return False, "Receipt has no accounting entries", None

        # Update status
        updated = await ReceiptCRUD.update_status(
            session, receipt, ReceiptStatus.APPROVED, reviewed_by=username
        )

        return True, "Receipt approved", updated

    @staticmethod
    async def unapprove_receipt(
        session: AsyncSession,
        receipt_id: int,
        username: str,
    ) -> Tuple[bool, str, Optional[Receipt]]:
        """
        Unapprove receipt (APPROVED → PENDING_REVIEW).
        Returns receipt to pending review for corrections.
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", None

        if receipt.status != ReceiptStatus.APPROVED:
            return False, "Receipt is not approved", None

        # Update status back to pending review
        updated = await ReceiptCRUD.update_status(
            session, receipt, ReceiptStatus.PENDING_REVIEW
        )

        return True, "Receipt returned to pending review", updated

    @staticmethod
    async def reject_receipt(
        session: AsyncSession,
        receipt_id: int,
        username: str,
        reason: str,
    ) -> Tuple[bool, str, Optional[Receipt]]:
        """
        Reject receipt (PENDING_REVIEW → REJECTED).
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", None

        if receipt.status != ReceiptStatus.PENDING_REVIEW:
            return False, "Receipt is not pending review", None

        # Update status
        updated = await ReceiptCRUD.update_status(
            session,
            receipt,
            ReceiptStatus.REJECTED,
            reviewed_by=username,
            rejection_reason=reason,
        )

        return True, "Receipt rejected", updated

    @staticmethod
    async def resubmit_receipt(
        session: AsyncSession,
        receipt_id: int,
        username: str,
    ) -> Tuple[bool, str, Optional[Receipt]]:
        """
        Resubmit rejected receipt after corrections (REJECTED → PENDING_REVIEW).
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", None

        if receipt.status != ReceiptStatus.REJECTED:
            return False, "Receipt is not rejected", None

        if receipt.created_by != username:
            return False, "Only the creator can resubmit", None

        # Re-generate accounting entries
        entries = ReceiptService.generate_accounting_entries(receipt)
        await AccountingEntryCRUD.delete_all_for_receipt(session, receipt_id)
        await AccountingEntryCRUD.create_bulk(session, receipt_id, entries, is_auto_generated=True)

        # Refresh receipt to clear stale relationship references after entry deletion
        await session.refresh(receipt)

        # Update status
        updated = await ReceiptCRUD.update_status(
            session, receipt, ReceiptStatus.PENDING_REVIEW
        )

        # Reload with entries
        updated = await ReceiptCRUD.get_by_id(session, receipt_id)

        return True, "Receipt resubmitted for review", updated

    @staticmethod
    async def regenerate_entries(
        session: AsyncSession,
        receipt_id: int,
        username: str,
    ) -> Tuple[bool, str, List[AccountingEntryCreate]]:
        """
        Regenerate accounting entries for a receipt.
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", []

        if receipt.status not in [ReceiptStatus.DRAFT, ReceiptStatus.PENDING_REVIEW]:
            return False, "Cannot regenerate entries for this receipt status", []

        # Generate new entries
        entries = ReceiptService.generate_accounting_entries(receipt)

        # Replace existing entries
        await AccountingEntryCRUD.delete_all_for_receipt(session, receipt_id)
        await AccountingEntryCRUD.create_bulk(session, receipt_id, entries, is_auto_generated=True)

        return True, "Entries regenerated", entries

    @staticmethod
    async def update_entries(
        session: AsyncSession,
        receipt_id: int,
        entries: List[AccountingEntryCreate],
        username: str,
    ) -> Tuple[bool, str, List]:
        """
        Update accounting entries for a receipt (accountant action).
        """
        receipt = await ReceiptCRUD.get_by_id(session, receipt_id)

        if not receipt:
            return False, "Receipt not found", []

        if receipt.status != ReceiptStatus.PENDING_REVIEW:
            return False, "Can only modify entries for receipts pending review", []

        # Validate entries
        is_valid, error = await AccountingEntryCRUD.validate_entries(entries)
        if not is_valid:
            return False, error, []

        # Replace entries
        updated_entries = await AccountingEntryCRUD.replace_all_for_receipt(
            session, receipt_id, entries, username
        )

        return True, "Entries updated", updated_entries

    @staticmethod
    async def get_pending_count(
        session: AsyncSession,
        company_id: Optional[int] = None,
    ) -> int:
        """Get count of receipts pending review."""
        receipts = await ReceiptCRUD.get_pending_review(session, company_id)
        return len(receipts)
