from __future__ import annotations

from datetime import date

from sqlalchemy import or_, select
from sqlalchemy.orm import Session, selectinload

from app.modules.clients.models import Client
from app.modules.core.iam.models import User
from app.modules.core.rbac.models import Role, UserRole
from app.modules.core.tenancy.models import Tenant
from app.modules.services.models import ClientServiceSubscription, FirmServiceSelection, ServiceCatalogue

SUBSCRIPTION_STATUSES = [
    ("draft", "Draft"),
    ("active", "Active"),
    ("on_hold", "On Hold"),
    ("completed", "Completed"),
    ("cancelled", "Cancelled"),
    ("inactive", "Inactive"),
]

ASSIGNMENT_ROLE_NAMES = ("Partner", "Branch Manager", "Staff")


def current_financial_year(today: date | None = None) -> str:
    today = today or date.today()
    if today.month >= 4:
        start = today.year
    else:
        start = today.year - 1
    return f"{start}-{str(start + 1)[-2:]}"


def assessment_year_from_financial_year(financial_year: str | None) -> str | None:
    if not financial_year or "-" not in financial_year:
        return None
    start = int(str(financial_year).split("-")[0])
    return f"{start + 1}-{str(start + 2)[-2:]}"


def normalize_financial_year(value: str | None) -> str:
    value = (value or "").strip()
    return value or current_financial_year()


def parse_date(value: str | None) -> date | None:
    if not value:
        return None
    value = value.strip()
    if not value:
        return None
    return date.fromisoformat(value)


def list_subscription_payload(
    db: Session,
    *,
    tenant_id: int,
    branch_id: int | None = None,
    financial_year: str | None = None,
    q: str = "",
    include_inactive: bool = True,
):
    fy = normalize_financial_year(financial_year)
    query = (
        select(ClientServiceSubscription)
        .options(
            selectinload(ClientServiceSubscription.client),
            selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceSubscription.assigned_partner),
            selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceSubscription.assigned_staff),
            selectinload(ClientServiceSubscription.review_partner),
            selectinload(ClientServiceSubscription.due_date_rule),
        )
        .where(
            ClientServiceSubscription.tenant_id == tenant_id,
            ClientServiceSubscription.financial_year == fy,
        )
    )

    if branch_id:
        query = query.where(ClientServiceSubscription.branch_id == branch_id)

    if not include_inactive:
        query = query.where(ClientServiceSubscription.is_active.is_(True))

    if q.strip():
        term = f"%{q.strip()}%"
        query = (
            query.join(Client, Client.id == ClientServiceSubscription.client_id)
            .join(ServiceCatalogue, ServiceCatalogue.id == ClientServiceSubscription.service_catalogue_id)
            .where(
                or_(
                    Client.client_name.ilike(term),
                    Client.client_code.ilike(term),
                    ServiceCatalogue.service_code.ilike(term),
                    ServiceCatalogue.service_name.ilike(term),
                )
            )
        )

    return db.execute(
        query.order_by(
            ClientServiceSubscription.is_locked.asc(),
            ClientServiceSubscription.is_active.desc(),
            ClientServiceSubscription.id.desc(),
        )
    ).scalars().all()


def get_subscription(db: Session, *, subscription_id: int, tenant_id: int) -> ClientServiceSubscription | None:
    return db.execute(
        select(ClientServiceSubscription)
        .options(
            selectinload(ClientServiceSubscription.client),
            selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceSubscription.assigned_partner),
            selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceSubscription.assigned_staff),
            selectinload(ClientServiceSubscription.review_partner),
            selectinload(ClientServiceSubscription.due_date_rule),
        )
        .where(
            ClientServiceSubscription.id == subscription_id,
            ClientServiceSubscription.tenant_id == tenant_id,
        )
    ).scalar_one_or_none()


def get_existing_subscription(
    db: Session,
    *,
    tenant_id: int,
    client_id: int,
    service_catalogue_id: int,
    financial_year: str | None = None,
) -> ClientServiceSubscription | None:
    return db.execute(
        select(ClientServiceSubscription).where(
            ClientServiceSubscription.tenant_id == tenant_id,
            ClientServiceSubscription.client_id == client_id,
            ClientServiceSubscription.service_catalogue_id == service_catalogue_id,
            ClientServiceSubscription.financial_year == normalize_financial_year(financial_year),
        )
    ).scalar_one_or_none()


def list_clients_for_assignment(db: Session, *, tenant_id: int, branch_id: int | None = None, partner_id: int | None = None):
    query = select(Client).where(Client.tenant_id == tenant_id)
    if branch_id:
        query = query.where(Client.branch_id == branch_id)
    if partner_id:
        query = query.where(Client.partner_id == partner_id)
    return db.execute(query.order_by(Client.client_name.asc())).scalars().all()


def list_enabled_services_for_assignment(db: Session, *, tenant_id: int):
    return db.execute(
        select(FirmServiceSelection)
        .join(ServiceCatalogue, ServiceCatalogue.id == FirmServiceSelection.service_catalogue_id)
        .options(selectinload(FirmServiceSelection.catalogue))
        .where(
            FirmServiceSelection.tenant_id == tenant_id,
            FirmServiceSelection.is_enabled.is_(True),
            ServiceCatalogue.is_active.is_(True),
        )
        .order_by(ServiceCatalogue.sort_order.asc(), ServiceCatalogue.service_name.asc())
    ).scalars().all()


def get_enabled_firm_service(db: Session, *, tenant_id: int, service_catalogue_id: int) -> FirmServiceSelection | None:
    return db.execute(
        select(FirmServiceSelection).where(
            FirmServiceSelection.tenant_id == tenant_id,
            FirmServiceSelection.service_catalogue_id == service_catalogue_id,
            FirmServiceSelection.is_enabled.is_(True),
        )
    ).scalar_one_or_none()


def list_assignable_users(db: Session, *, tenant_id: int, branch_id: int | None = None, role_names: tuple[str, ...] = ASSIGNMENT_ROLE_NAMES):
    query = (
        select(User)
        .join(UserRole, UserRole.user_id == User.id)
        .join(Role, Role.id == UserRole.role_id)
        .where(User.tenant_id == tenant_id, User.is_active.is_(True), Role.name.in_(role_names))
    )
    if branch_id:
        query = query.where(or_(User.branch_id == branch_id, User.branch_id.is_(None)))
    return db.execute(query.order_by(User.full_name.asc(), User.email.asc()).distinct()).scalars().all()


def tenant_requires_review_partner(db: Session, *, tenant_id: int) -> bool:
    tenant = db.get(Tenant, tenant_id)
    firm_type = (getattr(tenant, "firm_type", None) or "partnership").strip().lower() if tenant else "partnership"
    return firm_type == "partnership"


def review_partner_required_for_engagement(db: Session, *, tenant_id: int, engagement_type: str | None) -> bool:
    return tenant_requires_review_partner(db, tenant_id=tenant_id) and (engagement_type or "").strip().lower() == "assurance"


def list_review_partners(db: Session, *, tenant_id: int, branch_id: int | None = None):
    return list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Partner",))
