from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from sqlalchemy import and_, func, or_, select
from sqlalchemy.orm import joinedload

from app.modules.clients.models import Client
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from app.modules.services.models import (
    ClientServiceSubscription,
    ClientServiceTaskInstance,
    EngagementClosureChecklist,
    ServiceCatalogue,
)


ASSURANCE_TYPE = "assurance"
APPROVED_STATUSES = {"approved", "completed", "complete"}
REVIEWED_STATUSES = {"reviewed", "approved", "completed", "complete", "not_required"}
EVIDENCE_OK_STATUSES = {"accepted", "approved", "reviewed", "uploaded", "not_required"}
CLOSURE_CLOSED_STATUSES = {"closed", "completed"}


@dataclass(frozen=True)
class AqmmScope:
    tenant_id: int | None
    branch_id: int | None
    own_user_id: int | None


def _clean(value: Any) -> str:
    return str(value or "").strip()


def _active_tenant_id(request, user) -> int | None:
    value = (
        request.session.get("active_tenant_id")
        or request.session.get("selected_tenant_id")
        or request.session.get("tenant_id")
        or getattr(user, "tenant_id", None)
    )
    return int(value) if value not in (None, "", 0, "0") else None


def _active_branch_id(request, user, permissions: set[str]) -> int | None:
    value = request.session.get("active_branch_id")
    if value in (None, "", 0, "0"):
        if "clients.cross_branch" in permissions or "services.cross_branch" in permissions:
            return None
        branch_id = getattr(user, "branch_id", None)
        return int(branch_id) if branch_id not in (None, "", 0, "0") else None
    return int(value)


def build_scope(db, request, user) -> AqmmScope:
    roles = set(get_user_roles(db, user.id))
    permissions = set(get_user_permissions(db, user.id))
    own_user_id = None
    if "clients.view.own_only" in permissions and "Firm Admin" not in roles:
        own_user_id = int(user.id)
    return AqmmScope(
        tenant_id=_active_tenant_id(request, user),
        branch_id=_active_branch_id(request, user, permissions),
        own_user_id=own_user_id,
    )


def can_view_aqmm_dashboard(db, user) -> bool:
    """AQMM dashboard is intentionally restricted to Firm Admin and Partner roles.

    Managers/staff may still complete assigned work and review actions through
    Work Tracker, but the consolidated AQMM monitoring dashboard is a leadership
    view and must not be exposed through broad service/client permissions.
    """
    roles = set(get_user_roles(db, user.id))
    return bool(roles.intersection({"Firm Admin", "Partner"}))


def _subscription_filters(scope: AqmmScope, financial_year: str = "", q: str = ""):
    filters = [ClientServiceSubscription.engagement_type == ASSURANCE_TYPE]
    if scope.tenant_id:
        filters.append(ClientServiceSubscription.tenant_id == scope.tenant_id)
    if scope.branch_id:
        filters.append(ClientServiceSubscription.branch_id == scope.branch_id)
    if financial_year:
        filters.append(ClientServiceSubscription.financial_year == financial_year)
    if scope.own_user_id:
        filters.append(
            or_(
                ClientServiceSubscription.assigned_partner_user_id == scope.own_user_id,
                ClientServiceSubscription.assigned_manager_user_id == scope.own_user_id,
                ClientServiceSubscription.assigned_staff_user_id == scope.own_user_id,
                ClientServiceSubscription.review_partner_user_id == scope.own_user_id,
                Client.partner_id == scope.own_user_id,
            )
        )
    if q:
        like = f"%{q.lower()}%"
        filters.append(
            or_(
                func.lower(Client.client_name).like(like),
                func.lower(Client.client_code).like(like),
                func.lower(ServiceCatalogue.service_name).like(like),
                func.lower(ServiceCatalogue.service_code).like(like),
            )
        )
    return filters


def _count(db, stmt) -> int:
    return int(db.execute(stmt).scalar_one() or 0)


def _subscription_id_subquery(scope: AqmmScope, financial_year: str = "", q: str = ""):
    return (
        select(ClientServiceSubscription.id)
        .join(Client, Client.id == ClientServiceSubscription.client_id)
        .join(ServiceCatalogue, ServiceCatalogue.id == ClientServiceSubscription.service_catalogue_id)
        .where(and_(*_subscription_filters(scope, financial_year, q)))
        .subquery()
    )


def _task_count(db, subq, *extra_filters) -> int:
    return _count(
        db,
        select(func.count(ClientServiceTaskInstance.id)).where(
            ClientServiceTaskInstance.subscription_id.in_(select(subq.c.id)),
            ClientServiceTaskInstance.is_aqmm_task.is_(True),
            *extra_filters,
        ),
    )


def _subscription_task_counts(db, subscription_id: int) -> dict[str, int]:
    base = [ClientServiceTaskInstance.subscription_id == subscription_id, ClientServiceTaskInstance.is_aqmm_task.is_(True)]
    total = _count(db, select(func.count(ClientServiceTaskInstance.id)).where(*base))
    mandatory = _count(db, select(func.count(ClientServiceTaskInstance.id)).where(*base, ClientServiceTaskInstance.aqmm_mandatory.is_(True)))
    evidence_pending = _count(
        db,
        select(func.count(ClientServiceTaskInstance.id)).where(
            *base,
            ClientServiceTaskInstance.aqmm_evidence_required.is_(True),
            ClientServiceTaskInstance.evidence_status.notin_(list(EVIDENCE_OK_STATUSES)),
        ),
    )
    manager_pending = _count(
        db,
        select(func.count(ClientServiceTaskInstance.id)).where(
            *base,
            ClientServiceTaskInstance.aqmm_manager_review_required.is_(True),
            ClientServiceTaskInstance.manager_review_status.notin_(list(REVIEWED_STATUSES)),
        ),
    )
    partner_pending = _count(
        db,
        select(func.count(ClientServiceTaskInstance.id)).where(
            *base,
            ClientServiceTaskInstance.aqmm_partner_review_required.is_(True),
            ClientServiceTaskInstance.partner_review_status.notin_(list(REVIEWED_STATUSES)),
        ),
    )
    review_partner_pending = _count(
        db,
        select(func.count(ClientServiceTaskInstance.id)).where(
            *base,
            ClientServiceTaskInstance.aqmm_review_partner_required.is_(True),
            ClientServiceTaskInstance.review_partner_review_status.notin_(list(REVIEWED_STATUSES)),
        ),
    )
    completed = _count(
        db,
        select(func.count(ClientServiceTaskInstance.id)).where(
            *base,
            or_(
                ClientServiceTaskInstance.aqmm_status.in_(["completed", "complete", "approved"]),
                ClientServiceTaskInstance.status.in_(["completed", "complete", "done"]),
            ),
        ),
    )
    return {
        "total": total,
        "mandatory": mandatory,
        "completed": completed,
        "evidence_pending": evidence_pending,
        "manager_pending": manager_pending,
        "partner_pending": partner_pending,
        "review_partner_pending": review_partner_pending,
    }


def build_aqmm_dashboard_payload(db, request, user, *, financial_year: str = "", q: str = "") -> dict[str, Any]:
    financial_year = _clean(financial_year)
    q = _clean(q)
    scope = build_scope(db, request, user)
    subq = _subscription_id_subquery(scope, financial_year, q)

    total_assurance = _count(db, select(func.count()).select_from(subq))
    aqmm_approved = _count(
        db,
        select(func.count(ClientServiceSubscription.id)).where(
            ClientServiceSubscription.id.in_(select(subq.c.id)),
            ClientServiceSubscription.quality_workflow_status.in_(list(APPROVED_STATUSES)),
        ),
    )
    aqmm_pending = max(total_assurance - aqmm_approved, 0)

    kyc_pending = _count(
        db,
        select(func.count(ClientServiceSubscription.id)).where(
            ClientServiceSubscription.id.in_(select(subq.c.id)),
            ClientServiceSubscription.quality_kyc_status.notin_(list(APPROVED_STATUSES) + ["not_required"]),
        ),
    )
    declaration_pending = _count(
        db,
        select(func.count(ClientServiceSubscription.id)).where(
            ClientServiceSubscription.id.in_(select(subq.c.id)),
            or_(
                ClientServiceSubscription.quality_independence_status.notin_(list(APPROVED_STATUSES) + ["not_required"]),
                ClientServiceSubscription.quality_conflict_status.notin_(list(APPROVED_STATUSES) + ["not_required"]),
            ),
        ),
    )
    letter_pending = _count(
        db,
        select(func.count(ClientServiceSubscription.id)).where(
            ClientServiceSubscription.id.in_(select(subq.c.id)),
            ClientServiceSubscription.quality_engagement_letter_status.notin_(list(APPROVED_STATUSES) + ["not_required"]),
        ),
    )

    aqmm_tasks_total = _task_count(db, subq)
    evidence_pending = _task_count(
        db,
        subq,
        ClientServiceTaskInstance.aqmm_evidence_required.is_(True),
        ClientServiceTaskInstance.evidence_status.notin_(list(EVIDENCE_OK_STATUSES)),
    )
    manager_review_pending = _task_count(
        db,
        subq,
        ClientServiceTaskInstance.aqmm_manager_review_required.is_(True),
        ClientServiceTaskInstance.manager_review_status.notin_(list(REVIEWED_STATUSES)),
    )
    partner_review_pending = _task_count(
        db,
        subq,
        ClientServiceTaskInstance.aqmm_partner_review_required.is_(True),
        ClientServiceTaskInstance.partner_review_status.notin_(list(REVIEWED_STATUSES)),
    )
    review_partner_pending = _task_count(
        db,
        subq,
        ClientServiceTaskInstance.aqmm_review_partner_required.is_(True),
        ClientServiceTaskInstance.review_partner_review_status.notin_(list(REVIEWED_STATUSES)),
    )
    rework_pending = _task_count(
        db,
        subq,
        ClientServiceTaskInstance.rework_status.in_(["requested", "pending", "rework_required"]),
    )

    closure_closed = _count(
        db,
        select(func.count(EngagementClosureChecklist.id)).where(
            EngagementClosureChecklist.subscription_id.in_(select(subq.c.id)),
            EngagementClosureChecklist.closure_status.in_(list(CLOSURE_CLOSED_STATUSES)),
        ),
    )
    closure_pending = max(total_assurance - closure_closed, 0)

    engagement_rows = db.execute(
        select(ClientServiceSubscription, EngagementClosureChecklist)
        .join(Client, Client.id == ClientServiceSubscription.client_id)
        .join(ServiceCatalogue, ServiceCatalogue.id == ClientServiceSubscription.service_catalogue_id)
        .outerjoin(EngagementClosureChecklist, EngagementClosureChecklist.subscription_id == ClientServiceSubscription.id)
        .options(
            joinedload(ClientServiceSubscription.client),
            joinedload(ClientServiceSubscription.catalogue),
            joinedload(ClientServiceSubscription.assigned_partner),
            joinedload(ClientServiceSubscription.assigned_manager),
            joinedload(ClientServiceSubscription.assigned_staff),
            joinedload(ClientServiceSubscription.review_partner),
        )
        .where(and_(*_subscription_filters(scope, financial_year, q)))
        .order_by(ClientServiceSubscription.updated_at_utc.desc(), ClientServiceSubscription.id.desc())
        .limit(50)
    ).all()

    engagements = []
    for subscription, closure in engagement_rows:
        counts = _subscription_task_counts(db, subscription.id)
        blockers = []
        if subscription.quality_workflow_status not in APPROVED_STATUSES:
            blockers.append("AQMM acceptance pending")
        if counts["evidence_pending"]:
            blockers.append(f"Evidence pending: {counts['evidence_pending']}")
        if counts["manager_pending"]:
            blockers.append(f"Manager review pending: {counts['manager_pending']}")
        if counts["partner_pending"]:
            blockers.append(f"Partner review pending: {counts['partner_pending']}")
        if counts["review_partner_pending"]:
            blockers.append(f"Review partner review pending: {counts['review_partner_pending']}")
        if closure and closure.closure_status not in CLOSURE_CLOSED_STATUSES:
            blockers.append("Closure pending")
        engagements.append({
            "row": subscription,
            "closure": closure,
            "counts": counts,
            "blockers": blockers,
        })

    pending_tasks = db.execute(
        select(ClientServiceTaskInstance)
        .options(joinedload(ClientServiceTaskInstance.subscription).joinedload(ClientServiceSubscription.client), joinedload(ClientServiceTaskInstance.subscription).joinedload(ClientServiceSubscription.catalogue))
        .where(
            ClientServiceTaskInstance.subscription_id.in_(select(subq.c.id)),
            ClientServiceTaskInstance.is_aqmm_task.is_(True),
            or_(
                and_(ClientServiceTaskInstance.aqmm_evidence_required.is_(True), ClientServiceTaskInstance.evidence_status.notin_(list(EVIDENCE_OK_STATUSES))),
                and_(ClientServiceTaskInstance.aqmm_manager_review_required.is_(True), ClientServiceTaskInstance.manager_review_status.notin_(list(REVIEWED_STATUSES))),
                and_(ClientServiceTaskInstance.aqmm_partner_review_required.is_(True), ClientServiceTaskInstance.partner_review_status.notin_(list(REVIEWED_STATUSES))),
                and_(ClientServiceTaskInstance.aqmm_review_partner_required.is_(True), ClientServiceTaskInstance.review_partner_review_status.notin_(list(REVIEWED_STATUSES))),
                ClientServiceTaskInstance.rework_status.in_(["requested", "pending", "rework_required"]),
            ),
        )
        .order_by(ClientServiceTaskInstance.internal_target_date.asc().nulls_last(), ClientServiceTaskInstance.sequence_no.asc())
        .limit(50)
    ).scalars().all()

    return {
        "financial_year": financial_year,
        "q": q,
        "scope": scope,
        "kpis": {
            "total_assurance": total_assurance,
            "aqmm_approved": aqmm_approved,
            "aqmm_pending": aqmm_pending,
            "declaration_pending": declaration_pending,
            "kyc_pending": kyc_pending,
            "engagement_letter_pending": letter_pending,
            "aqmm_tasks_total": aqmm_tasks_total,
            "evidence_pending": evidence_pending,
            "manager_review_pending": manager_review_pending,
            "partner_review_pending": partner_review_pending,
            "review_partner_pending": review_partner_pending,
            "rework_pending": rework_pending,
            "closure_pending": closure_pending,
            "closure_closed": closure_closed,
        },
        "engagements": engagements,
        "pending_tasks": pending_tasks,
    }
