"""
Inventory Use Cases — Stock Operations.

Single-purpose handlers for stock movement operations:
goods receipt, goods issue, transfer, adjustment.
"""
from dataclasses import dataclass
from decimal import Decimal
from uuid import uuid4

from ..dtos.inventory_dtos import (
    GoodsReceiptDTO,
    GoodsIssueDTO,
    StockTransferDTO,
    StockAdjustmentDTO,
    StockLedgerResponseDTO,
)
from ...domain.services.inventory_services import (
    StockTransactionService,
    CostingService,
)
from ...domain.repositories.interfaces import (
    IItemRepository,
    IStockLedgerRepository,
    IStockBalanceRepository,
    IBatchRepository,
    ISerialNumberRepository,
)
from ...domain.exceptions import (
    InsufficientStockError,
    InvalidTransactionError,
    StockFrozenError,
)


@dataclass
class PostGoodsReceiptUseCase:
    """
    Use Case — رسید کالا.

    Flow:
    1. اعتبارسنجی ورودی
    2. بررسی بچ/سریال
    3. ایجاد ثبت دفتر (SLE)
    4. بروزرسانی موجودی
    5. محاسبه بها
    """

    item_repo: IItemRepository
    ledger_repo: IStockLedgerRepository
    balance_repo: IStockBalanceRepository
    batch_repo: IBatchRepository = None
    serial_repo: ISerialNumberRepository = None

    def execute(self, dto: GoodsReceiptDTO, tenant_id=None) -> StockLedgerResponseDTO:
        quantity = Decimal(dto.quantity)
        unit_cost = Decimal(dto.unit_cost)

        # 1. Validate
        is_valid, msg = StockTransactionService.validate_goods_receipt(
            item_id=dto.item_id,
            warehouse_id=dto.warehouse_id,
            quantity=quantity,
        )
        if not is_valid:
            raise InvalidTransactionError(msg)

        # 2. Create SLE — positive quantity = inbound
        from ...domain.entities.stock import StockLedgerEntry
        sle = StockLedgerEntry(
            id=uuid4(),
            tenant_id=tenant_id,
            posting_date=dto.posting_date,
            item_id=dto.item_id,
            warehouse_id=dto.warehouse_id,
            location_id=dto.location_id,
            batch_id=dto.batch_id,
            transaction_type="GR",
            quantity=quantity,
            quantity_in_base_uom=quantity,
            uom_id=dto.uom_id,
            unit_cost=unit_cost,
            total_cost=quantity * unit_cost,
            source_document_type=dto.source_document_type,
            source_document_id=dto.source_document_id,
            remarks=dto.remarks,
        )
        saved_sle = self.ledger_repo.save(sle)

        # 3. Update balance
        balance = self.balance_repo.get_balance(
            item_id=dto.item_id,
            warehouse_id=dto.warehouse_id,
        )
        if balance:
            # Update WAC
            new_avg = CostingService.calculate_weighted_average_cost(
                balance.quantity_on_hand,
                balance.total_value,
                quantity,
                unit_cost,
            )
            balance.quantity_on_hand += quantity
            balance.quantity_available += quantity
            balance.average_unit_cost = new_avg
            balance.total_value = balance.quantity_on_hand * new_avg
            balance.last_movement_date = dto.posting_date
            self.balance_repo.save(balance)

        return StockLedgerResponseDTO(
            id=saved_sle.id,
            posting_date=dto.posting_date,
            transaction_type="GR",
            quantity=str(quantity),
            unit_cost=str(unit_cost),
            total_cost=str(quantity * unit_cost),
            remarks=dto.remarks,
        )


@dataclass
class PostGoodsIssueUseCase:
    """
    Use Case — حواله کالا.

    Flow:
    1. بررسی موجودی کافی
    2. بررسی بچ/سریال
    3. ایجاد SLE منفی
    4. بروزرسانی موجودی
    """

    item_repo: IItemRepository
    ledger_repo: IStockLedgerRepository
    balance_repo: IStockBalanceRepository

    def execute(self, dto: GoodsIssueDTO, tenant_id=None) -> StockLedgerResponseDTO:
        quantity = Decimal(dto.quantity)

        # 1. Check balance
        balance = self.balance_repo.get_balance(
            item_id=dto.item_id,
            warehouse_id=dto.warehouse_id,
        )
        if not balance:
            raise InsufficientStockError(
                str(dto.item_id), str(dto.warehouse_id), str(quantity)
            )

        is_valid, msg = StockTransactionService.validate_goods_issue(
            quantity_on_hand=balance.quantity_on_hand,
            quantity_reserved=balance.quantity_reserved,
            requested_quantity=quantity,
        )
        if not is_valid:
            raise InsufficientStockError(
                str(dto.item_id), str(dto.warehouse_id), msg
            )

        # 2. Create SLE — negative quantity = outbound
        from ...domain.entities.stock import StockLedgerEntry
        sle = StockLedgerEntry(
            id=uuid4(),
            tenant_id=tenant_id,
            posting_date=dto.posting_date,
            item_id=dto.item_id,
            warehouse_id=dto.warehouse_id,
            location_id=dto.location_id,
            batch_id=dto.batch_id,
            transaction_type="GI",
            quantity=-quantity,
            quantity_in_base_uom=-quantity,
            uom_id=dto.uom_id,
            unit_cost=balance.average_unit_cost,
            total_cost=quantity * balance.average_unit_cost,
            source_document_type=dto.source_document_type,
            source_document_id=dto.source_document_id,
            reason_code=dto.reason_code,
            remarks=dto.remarks,
        )
        saved_sle = self.ledger_repo.save(sle)

        # 3. Update balance
        balance.quantity_on_hand -= quantity
        balance.quantity_available -= quantity
        balance.total_value = balance.quantity_on_hand * balance.average_unit_cost
        balance.last_movement_date = dto.posting_date
        self.balance_repo.save(balance)

        return StockLedgerResponseDTO(
            id=saved_sle.id,
            posting_date=dto.posting_date,
            transaction_type="GI",
            quantity=str(-quantity),
            unit_cost=str(balance.average_unit_cost),
            total_cost=str(quantity * balance.average_unit_cost),
            remarks=dto.remarks,
        )


@dataclass
class TransferStockUseCase:
    """Use Case — انتقال بین انبارها."""

    item_repo: IItemRepository
    ledger_repo: IStockLedgerRepository
    balance_repo: IStockBalanceRepository

    def execute(self, dto: StockTransferDTO, tenant_id=None) -> StockLedgerResponseDTO:
        quantity = Decimal(dto.quantity)

        # Check source balance
        from_balance = self.balance_repo.get_balance(
            item_id=dto.item_id,
            warehouse_id=dto.from_warehouse_id,
        )
        if not from_balance:
            raise InsufficientStockError(
                str(dto.item_id), str(dto.from_warehouse_id), str(quantity)
            )

        is_valid, msg = StockTransactionService.validate_transfer(
            from_warehouse_id=dto.from_warehouse_id,
            to_warehouse_id=dto.to_warehouse_id,
            quantity=quantity,
            available_quantity=from_balance.quantity_available,
        )
        if not is_valid:
            raise InvalidTransactionError(msg)

        unit_cost = from_balance.average_unit_cost

        # Issue from source
        from ...domain.entities.stock import StockLedgerEntry
        sle_out = StockLedgerEntry(
            id=uuid4(),
            tenant_id=tenant_id,
            posting_date=dto.posting_date,
            item_id=dto.item_id,
            warehouse_id=dto.from_warehouse_id,
            transaction_type="TRANSFER",
            transaction_sub_type="OUT",
            quantity=-quantity,
            quantity_in_base_uom=-quantity,
            uom_id=dto.uom_id,
            unit_cost=unit_cost,
            total_cost=quantity * unit_cost,
            remarks=dto.remarks,
        )
        self.ledger_repo.save(sle_out)

        # Receive at destination
        sle_in = StockLedgerEntry(
            id=uuid4(),
            tenant_id=tenant_id,
            posting_date=dto.posting_date,
            item_id=dto.item_id,
            warehouse_id=dto.to_warehouse_id,
            transaction_type="TRANSFER",
            transaction_sub_type="IN",
            quantity=quantity,
            quantity_in_base_uom=quantity,
            uom_id=dto.uom_id,
            unit_cost=unit_cost,
            total_cost=quantity * unit_cost,
            remarks=dto.remarks,
        )
        saved_sle_in = self.ledger_repo.save(sle_in)

        # Update balances
        from_balance.quantity_on_hand -= quantity
        from_balance.quantity_available -= quantity
        from_balance.total_value = from_balance.quantity_on_hand * from_balance.average_unit_cost
        from_balance.last_movement_date = dto.posting_date
        self.balance_repo.save(from_balance)

        to_balance = self.balance_repo.get_balance(
            item_id=dto.item_id,
            warehouse_id=dto.to_warehouse_id,
        )
        if to_balance:
            new_avg = CostingService.calculate_weighted_average_cost(
                to_balance.quantity_on_hand,
                to_balance.total_value,
                quantity,
                unit_cost,
            )
            to_balance.quantity_on_hand += quantity
            to_balance.quantity_available += quantity
            to_balance.average_unit_cost = new_avg
            to_balance.total_value = to_balance.quantity_on_hand * new_avg
            to_balance.last_movement_date = dto.posting_date
            self.balance_repo.save(to_balance)

        return StockLedgerResponseDTO(
            id=saved_sle_in.id,
            posting_date=dto.posting_date,
            transaction_type="TRANSFER",
            quantity=str(quantity),
            unit_cost=str(unit_cost),
            total_cost=str(quantity * unit_cost),
            remarks=dto.remarks,
        )
