"""
Branch inventory rules:
- Sell / POS: only stock with allocation_status='received' at the operating branch.
- Cashiers & staff: operating branch = users.branch_id (set at user creation / login).
- Managers: receive allocations at their branch; may sell per role (same branch rules when not universal).
"""
from __future__ import annotations

from typing import Optional

from sqlalchemy.orm import Session

from app.models.inventory_allocation import BranchInventoryAllocation
from app.models.user import User

BRANCH_SPECIFIC_ROLES = frozenset({"manager", "cashier", "staff"})
UNIVERSAL_ROLES = frozenset({"super_admin", "admin", "accountant"})
STOCK_RECEIVE_ROLES = frozenset({"manager", "super_admin", "admin"})


def get_received_available(db: Session, product_id: str, branch_id: Optional[str]) -> int:
    """Quantity sellable at a branch (received allocations only)."""
    if not branch_id:
        return 0
    row = (
        db.query(BranchInventoryAllocation)
        .filter(
            BranchInventoryAllocation.product_id == product_id,
            BranchInventoryAllocation.branch_id == branch_id,
            BranchInventoryAllocation.allocation_status == "received",
        )
        .first()
    )
    return int(row.available_quantity or 0) if row else 0


def assert_sufficient_branch_stock(
    db: Session,
    product_id: str,
    branch_id: Optional[str],
    quantity: int,
    product_name: str = "Product",
) -> None:
    available = get_received_available(db, product_id, branch_id)
    if quantity > available:
        raise ValueError(
            f"Insufficient stock at this branch for '{product_name}': "
            f"{available} available (received allocation), {quantity} requested."
        )


def deduct_received_branch_stock(
    db: Session,
    product_id: str,
    branch_id: Optional[str],
    quantity: int,
) -> bool:
    """Deduct from received branch allocation. Returns False if insufficient."""
    if not branch_id or quantity <= 0:
        return False
    alloc = (
        db.query(BranchInventoryAllocation)
        .filter(
            BranchInventoryAllocation.product_id == product_id,
            BranchInventoryAllocation.branch_id == branch_id,
            BranchInventoryAllocation.allocation_status == "received",
        )
        .first()
    )
    if not alloc or int(alloc.available_quantity or 0) < quantity:
        return False
    alloc.available_quantity = int(alloc.available_quantity or 0) - quantity
    return True


def resolve_operating_branch_id(
    user: Optional[User],
    requested_branch_id: Optional[str] = None,
) -> Optional[str]:
    """
    Branch used for stock checks and sales.
    Branch-specific users are locked to user.branch_id.
    """
    if not user:
        return requested_branch_id

    role = (user.role or "").lower()
    if role in BRANCH_SPECIFIC_ROLES:
        if not user.branch_id:
            raise ValueError(
                f"User '{user.username}' ({role}) has no branch assigned. "
                "Assign a branch before selling stock."
            )
        if requested_branch_id and str(requested_branch_id) != str(user.branch_id):
            raise ValueError("You can only use inventory at your assigned branch.")
        return str(user.branch_id)

    if requested_branch_id:
        return str(requested_branch_id)
    if user.branch_id:
        return str(user.branch_id)
    return None


def assert_user_may_access_branch(user: User, branch_id: str, action: str = "access") -> None:
    role = (user.role or "").lower()
    if role in UNIVERSAL_ROLES:
        return
    if role in BRANCH_SPECIFIC_ROLES:
        if not user.branch_id:
            raise ValueError(f"Your account has no branch assigned; cannot {action} branch inventory.")
        if str(user.branch_id) != str(branch_id):
            raise ValueError(f"You can only {action} inventory for your assigned branch.")
        return
    raise ValueError(f"Role '{user.role}' cannot {action} branch inventory.")


def user_may_receive_stock(user: User) -> bool:
    return (user.role or "").lower() in STOCK_RECEIVE_ROLES


def assert_user_may_receive_stock(user: User) -> None:
    if not user_may_receive_stock(user):
        raise ValueError(
            "Only managers and administrators can receive stock at a branch. "
            "Cashiers sell stock that has already been received."
        )


def add_received_branch_stock(
    db: Session,
    product_id: str,
    branch_id: str,
    quantity: int,
    *,
    cost_per_unit: Optional[float] = None,
) -> BranchInventoryAllocation:
    """Increase received available qty at a branch (e.g. after inter-branch transfer in)."""
    from datetime import datetime
    import uuid

    if not branch_id or quantity <= 0:
        raise ValueError("Invalid branch stock increase")

    row = (
        db.query(BranchInventoryAllocation)
        .filter(
            BranchInventoryAllocation.product_id == product_id,
            BranchInventoryAllocation.branch_id == branch_id,
            BranchInventoryAllocation.allocation_status == "received",
        )
        .first()
    )
    if row:
        row.available_quantity = int(row.available_quantity or 0) + quantity
        if cost_per_unit is not None and not row.allocated_cost_per_unit:
            row.allocated_cost_per_unit = cost_per_unit
        return row

    cost = cost_per_unit or 0
    row = BranchInventoryAllocation(
        id=str(uuid.uuid4()),
        product_id=product_id,
        branch_id=branch_id,
        allocated_quantity=quantity,
        received_quantity=quantity,
        available_quantity=quantity,
        reserved_quantity=0,
        allocated_cost_per_unit=cost,
        total_allocated_cost=cost * quantity,
        allocation_status="received",
        allocation_date=datetime.utcnow(),
        actual_delivery_date=datetime.utcnow().date(),
        notes="POS inter-branch transfer receipt",
    )
    db.add(row)
    db.flush()
    return row


def transfer_received_stock_between_branches(
    db: Session,
    product_id: str,
    from_branch_id: str,
    to_branch_id: str,
    quantity: int,
) -> None:
    """Move received stock from one branch to another (for POS transfer-to-selling-branch)."""
    if from_branch_id == to_branch_id:
        return
    src = (
        db.query(BranchInventoryAllocation)
        .filter(
            BranchInventoryAllocation.product_id == product_id,
            BranchInventoryAllocation.branch_id == from_branch_id,
            BranchInventoryAllocation.allocation_status == "received",
        )
        .first()
    )
    if not src or int(src.available_quantity or 0) < quantity:
        raise ValueError("Insufficient stock at source branch for transfer")

    cost = float(src.allocated_cost_per_unit or 0)
    if not deduct_received_branch_stock(db, product_id, from_branch_id, quantity):
        raise ValueError("Could not deduct stock at source branch")
    add_received_branch_stock(
        db, product_id, to_branch_id, quantity, cost_per_unit=cost
    )


def sum_other_branches_available(
    db: Session, product_id: str, exclude_branch_id: Optional[str]
) -> int:
    q = db.query(BranchInventoryAllocation).filter(
        BranchInventoryAllocation.product_id == product_id,
        BranchInventoryAllocation.allocation_status == "received",
        BranchInventoryAllocation.available_quantity > 0,
    )
    if exclude_branch_id:
        q = q.filter(BranchInventoryAllocation.branch_id != exclude_branch_id)
    rows = q.all()
    return sum(int(r.available_quantity or 0) for r in rows)
