from __future__ import annotations

from collections import Counter
from datetime import date
from typing import Any

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

from app.modules.documents.models import EngagementDocument, PermanentClientDocument
from app.modules.services.models import (
    ClientServiceSubscription,
    ClientServiceTaskInstance,
    ServiceTaskComment,
)

OPEN_TASK_STATUSES = {"pending", "in_progress", "blocked", "ready_for_review", "rework"}
CLOSED_TASK_STATUSES = {"completed", "approved", "closed", "not_applicable"}


def _client_id(client_row: dict[str, Any]) -> int:
    return int(client_row.get("id") or 0)


def _tenant_id(client_row: dict[str, Any]) -> int:
    return int(client_row.get("tenant_id") or 0)


def list_client_engagements(db: Session, client_row: dict[str, Any], *, limit: int = 200, financial_year: str | None = None) -> list[ClientServiceSubscription]:
    """Return engagements/subscriptions visible to the logged-in client."""
    query = (
        select(ClientServiceSubscription)
        .options(
            selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceSubscription.assigned_partner),
            selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceSubscription.assigned_staff),
        )
        .where(
            ClientServiceSubscription.tenant_id == _tenant_id(client_row),
            ClientServiceSubscription.client_id == _client_id(client_row),
            ClientServiceSubscription.is_active.is_(True),
        )
    )
    if financial_year:
        query = query.where(ClientServiceSubscription.financial_year == financial_year.strip())
    rows = db.execute(
        query.order_by(
            ClientServiceSubscription.current_due_date.asc().nulls_last(),
            ClientServiceSubscription.updated_at_utc.desc(),
        )
        .limit(max(1, min(int(limit or 200), 500)))
    ).scalars().all()
    return rows


def list_client_tasks_for_engagement(db: Session, client_row: dict[str, Any], engagement_id: int) -> list[ClientServiceTaskInstance]:
    return db.execute(
        select(ClientServiceTaskInstance)
        .options(
            selectinload(ClientServiceTaskInstance.catalogue),
            selectinload(ClientServiceTaskInstance.assigned_to),
            selectinload(ClientServiceTaskInstance.comments).selectinload(ServiceTaskComment.created_by),
        )
        .where(
            ClientServiceTaskInstance.tenant_id == _tenant_id(client_row),
            ClientServiceTaskInstance.client_id == _client_id(client_row),
            ClientServiceTaskInstance.subscription_id == int(engagement_id),
            ClientServiceTaskInstance.is_active.is_(True),
        )
        .order_by(ClientServiceTaskInstance.sequence_no.asc(), ClientServiceTaskInstance.id.asc())
    ).scalars().all()


def get_client_engagement(db: Session, client_row: dict[str, Any], engagement_id: int, *, financial_year: str | None = None) -> ClientServiceSubscription | None:
    query = (
        select(ClientServiceSubscription)
        .options(
            selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceSubscription.assigned_partner),
            selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceSubscription.assigned_staff),
        )
        .where(
            ClientServiceSubscription.id == int(engagement_id),
            ClientServiceSubscription.tenant_id == _tenant_id(client_row),
            ClientServiceSubscription.client_id == _client_id(client_row),
            ClientServiceSubscription.is_active.is_(True),
        )
    )
    if financial_year:
        query = query.where(ClientServiceSubscription.financial_year == financial_year.strip())
    return db.execute(query).scalar_one_or_none()


def get_client_task(db: Session, client_row: dict[str, Any], task_id: int, *, financial_year: str | None = None) -> ClientServiceTaskInstance | None:
    query = (
        select(ClientServiceTaskInstance)
        .where(
            ClientServiceTaskInstance.id == int(task_id),
            ClientServiceTaskInstance.tenant_id == _tenant_id(client_row),
            ClientServiceTaskInstance.client_id == _client_id(client_row),
            ClientServiceTaskInstance.is_active.is_(True),
        )
    )
    if financial_year:
        query = query.where(ClientServiceTaskInstance.financial_year == financial_year.strip())
    return db.execute(query).scalar_one_or_none()


def list_client_visible_comments(db: Session, client_row: dict[str, Any], *, limit: int = 100, financial_year: str | None = None) -> list[ServiceTaskComment]:
    query = (
        select(ServiceTaskComment)
        .options(
            selectinload(ServiceTaskComment.created_by),
            selectinload(ServiceTaskComment.task).selectinload(ClientServiceTaskInstance.catalogue),
            selectinload(ServiceTaskComment.subscription).selectinload(ClientServiceSubscription.catalogue),
        )
        .join(ClientServiceTaskInstance, ClientServiceTaskInstance.id == ServiceTaskComment.task_instance_id)
        .where(
            ServiceTaskComment.tenant_id == _tenant_id(client_row),
            ServiceTaskComment.visibility == "client",
            ServiceTaskComment.is_deleted.is_(False),
            ClientServiceTaskInstance.client_id == _client_id(client_row),
            ClientServiceTaskInstance.tenant_id == _tenant_id(client_row),
            ClientServiceTaskInstance.is_active.is_(True),
        )
    )
    if financial_year:
        query = query.where(ClientServiceTaskInstance.financial_year == financial_year.strip())
    return db.execute(
        query.order_by(ServiceTaskComment.created_at_utc.desc(), ServiceTaskComment.id.desc())
        .limit(max(1, min(int(limit or 100), 300)))
    ).scalars().all()


def create_client_reply(db: Session, *, client_row: dict[str, Any], task: ClientServiceTaskInstance, message: str, user) -> ServiceTaskComment:
    clean_message = (message or "").strip()
    if not clean_message:
        raise ValueError("Reply message is required.")
    if len(clean_message) > 4000:
        raise ValueError("Reply message is too long. Please keep it within 4000 characters.")
    comment = ServiceTaskComment(
        tenant_id=task.tenant_id,
        branch_id=task.branch_id,
        subscription_id=task.subscription_id,
        task_instance_id=task.id,
        comment_type="client_clarification",
        visibility="client",
        message=clean_message,
        created_by_user_id=getattr(user, "id", None),
    )
    db.add(comment)
    db.flush()
    return comment


def list_client_engagement_documents(db: Session, client_row: dict[str, Any], *, engagement_id: int | None = None, financial_year: str | None = None) -> list[EngagementDocument]:
    stmt = (
        select(EngagementDocument)
        .options(selectinload(EngagementDocument.versions), selectinload(EngagementDocument.engagement).selectinload(ClientServiceSubscription.catalogue))
        .where(
            EngagementDocument.tenant_id == _tenant_id(client_row),
            EngagementDocument.client_id == _client_id(client_row),
            EngagementDocument.is_deleted.is_(False),
        )
    )
    if engagement_id is not None:
        stmt = stmt.where(EngagementDocument.engagement_id == int(engagement_id))
    if financial_year:
        stmt = stmt.where(EngagementDocument.financial_year == financial_year.strip())
    return db.execute(stmt.order_by(EngagementDocument.updated_at_utc.desc())).unique().scalars().all()


def list_client_permanent_documents(db: Session, client_row: dict[str, Any]) -> list[PermanentClientDocument]:
    return db.execute(
        select(PermanentClientDocument)
        .options(selectinload(PermanentClientDocument.versions))
        .where(
            PermanentClientDocument.tenant_id == _tenant_id(client_row),
            PermanentClientDocument.client_id == _client_id(client_row),
            PermanentClientDocument.is_deleted.is_(False),
        )
        .order_by(PermanentClientDocument.category.asc(), PermanentClientDocument.updated_at_utc.desc())
    ).unique().scalars().all()


def build_client_portal_summary(db: Session, client_row: dict[str, Any], *, financial_year: str | None = None) -> dict[str, Any]:
    engagements = list_client_engagements(db, client_row, limit=500, financial_year=financial_year)
    engagement_ids = [row.id for row in engagements]
    today = date.today()

    task_rows: list[ClientServiceTaskInstance] = []
    if engagement_ids:
        task_rows = db.execute(
            select(ClientServiceTaskInstance).where(
                ClientServiceTaskInstance.tenant_id == _tenant_id(client_row),
                ClientServiceTaskInstance.client_id == _client_id(client_row),
                ClientServiceTaskInstance.subscription_id.in_(engagement_ids),
                ClientServiceTaskInstance.is_active.is_(True),
            )
        ).scalars().all()

    status_counter = Counter((task.status or "pending") for task in task_rows)
    open_tasks = [task for task in task_rows if (task.status or "pending") in OPEN_TASK_STATUSES]
    overdue_tasks = [
        task for task in open_tasks
        if task.internal_target_date is not None and task.internal_target_date < today
    ]
    due_soon_engagements = [
        row for row in engagements
        if row.current_due_date is not None and row.current_due_date >= today
    ][:10]

    pending_from_client = 0
    with_firm = 0
    completed = 0
    clarification_required = 0
    for row in engagements:
        tasks_for_eng = [t for t in task_rows if t.subscription_id == row.id]
        statuses = {(t.status or "pending") for t in tasks_for_eng}
        if statuses & {"blocked", "client_pending", "clarification_required"}:
            clarification_required += 1
        elif tasks_for_eng and all((t.status or "pending") in CLOSED_TASK_STATUSES for t in tasks_for_eng):
            completed += 1
        elif statuses & {"pending"}:
            pending_from_client += 1
        else:
            with_firm += 1

    return {
        "engagements": engagements,
        "task_rows": task_rows,
        "status_counter": status_counter,
        "total_engagements": len(engagements),
        "open_tasks": len(open_tasks),
        "overdue_tasks": len(overdue_tasks),
        "completed_tasks": status_counter.get("completed", 0) + status_counter.get("approved", 0) + status_counter.get("closed", 0),
        "due_soon_engagements": due_soon_engagements,
        "pending_from_client": pending_from_client,
        "with_firm": with_firm,
        "clarification_required": clarification_required,
        "completed_engagements": completed,
    }
