from __future__ import annotations

from collections import defaultdict
from datetime import date, datetime, timezone
from typing import Any

from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse
from sqlalchemy import or_, select
from sqlalchemy.orm import Session, selectinload

from app.core.db.common import CommonSessionLocal
from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
from app.core.security.session_auth import get_current_user
from app.core.templating import templates
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from app.modules.documents.models import EngagementDocument
from app.modules.clients.models import Client
from app.modules.services.execution import CLOSED_TASK_STATUSES, TASK_PRIORITIES, TASK_STATUSES
from app.modules.services.models import (
    ClientServiceSubscription,
    ClientServiceTaskInstance,
    ServiceCatalogue,
    ServiceTaskComment,
)

router = APIRouter(prefix="/partner", tags=["partner-workspace-ui"])

REVIEW_ACTIONS = {
    "approve": ("completed", "partner_review", "partner_review"),
    "send_rework": ("pending", "partner_review_note", "partner_review"),
    "clarification": ("blocked", "client_clarification", "internal"),
}


def _redirect_login():
    return RedirectResponse(url="/login", status_code=303)


def _redirect_denied():
    from app.core.http_responses import ui_access_denied
    return ui_access_denied()


def _is_partner_user(db: Session, current_user) -> bool:
    roles = set(get_user_roles(db, current_user.id))
    return bool(roles.intersection({"Partner", "Firm Admin", "System Admin"}))


def _base_ctx(request: Request, db: Session, current_user, **ctx):
    base = {
        "request": request,
        "current_user": current_user,
        "current_user_roles": get_user_roles(db, current_user.id),
        "current_user_permissions": get_user_permissions(db, current_user.id),
        "csrf_token": get_or_create_csrf_token(request),
        "task_statuses": TASK_STATUSES,
        "task_priorities": TASK_PRIORITIES,
    }
    base.update(ctx)
    return base


def _render(request: Request, template_name: str, db: Session, current_user, **ctx):
    return templates.TemplateResponse(template_name, _base_ctx(request, db, current_user, **ctx))


def _active_tenant_branch(request: Request, current_user, roles: set[str]) -> tuple[int, int | None]:
    tenant_id = request.session.get("active_tenant_id") or current_user.tenant_id
    branch_id = request.session.get("active_branch_id")
    if "System Admin" not in roles:
        tenant_id = current_user.tenant_id
    if not roles.intersection({"System Admin", "Firm Admin"}):
        branch_id = current_user.branch_id
    if branch_id in (0, "0", "", None):
        branch_id = None
    return int(tenant_id), int(branch_id) if branch_id is not None else None




def _active_financial_year(request: Request) -> str | None:
    value = request.session.get("active_financial_year") or getattr(request.state, "year_code", None)
    value = (value or "").strip()
    return value or None

def _subscription_scope_filter(stmt, tenant_id: int, branch_id: int | None, current_user, roles: set[str], financial_year: str | None = None):
    stmt = stmt.where(ClientServiceSubscription.tenant_id == tenant_id, ClientServiceSubscription.is_active.is_(True))
    if branch_id is not None:
        stmt = stmt.where(ClientServiceSubscription.branch_id == branch_id)
    if financial_year:
        stmt = stmt.where(ClientServiceSubscription.financial_year == financial_year.strip())
    if not roles.intersection({"System Admin", "Firm Admin"}):
        stmt = stmt.where(
            or_(
                ClientServiceSubscription.assigned_partner_user_id == current_user.id,
                ClientServiceSubscription.review_partner_user_id == current_user.id,
            )
        )
    return stmt


def _task_scope_filter(stmt, tenant_id: int, branch_id: int | None, current_user, roles: set[str], financial_year: str | None = None):
    stmt = stmt.where(ClientServiceTaskInstance.tenant_id == tenant_id, ClientServiceTaskInstance.is_active.is_(True))
    if branch_id is not None:
        stmt = stmt.where(ClientServiceTaskInstance.branch_id == branch_id)
    if financial_year:
        stmt = stmt.where(ClientServiceTaskInstance.financial_year == financial_year.strip())
    if not roles.intersection({"System Admin", "Firm Admin"}):
        stmt = stmt.where(
            ClientServiceTaskInstance.subscription.has(
                or_(
                    ClientServiceSubscription.assigned_partner_user_id == current_user.id,
                    ClientServiceSubscription.review_partner_user_id == current_user.id,
                )
            )
        )
    return stmt


def _task_status_label(task: ClientServiceTaskInstance) -> str:
    return dict(TASK_STATUSES).get(getattr(task, "status", ""), (getattr(task, "status", "") or "-").replace("_", " ").title())


def _task_priority_label(task: ClientServiceTaskInstance) -> str:
    return dict(TASK_PRIORITIES).get(getattr(task, "priority", ""), (getattr(task, "priority", "") or "normal").replace("_", " ").title())


def _engagement_label(subscription: ClientServiceSubscription | None, task: ClientServiceTaskInstance | None = None) -> str:
    catalogue = getattr(subscription, "catalogue", None) if subscription else getattr(task, "catalogue", None)
    service_name = getattr(catalogue, "service_name", None) or getattr(catalogue, "name", None) or "Engagement"
    fy = getattr(subscription, "financial_year", None) or getattr(task, "financial_year", None)
    return f"{service_name} · FY {fy}" if fy else str(service_name)


def _decorate_task(task: ClientServiceTaskInstance, today: date) -> ClientServiceTaskInstance:
    client = getattr(task, "client", None)
    assignee = getattr(task, "assigned_to", None)
    subscription = getattr(task, "subscription", None)
    target = getattr(task, "internal_target_date", None)
    status = (task.status or "pending").strip().lower()
    is_closed = status in CLOSED_TASK_STATUSES
    task.status_label = _task_status_label(task)
    task.priority_label = _task_priority_label(task)
    task.client_display = getattr(client, "client_name", None) or "Unlinked Client"
    task.client_code_display = getattr(client, "client_code", None) or ""
    task.assignee_display = getattr(assignee, "full_name", None) or getattr(assignee, "email", None) or "Unassigned"
    task.engagement_label = _engagement_label(subscription, task)
    task.is_overdue = bool(target and target < today and not is_closed)
    task.is_due_today = bool(target and target == today and not is_closed)
    task.comment_count = len([c for c in getattr(task, "comments", []) if not getattr(c, "is_deleted", False)])
    return task


def _task_bucket(task: ClientServiceTaskInstance) -> str:
    status = (task.status or "pending").strip().lower()
    if status == "blocked":
        return "clarification_required"
    if status == "completed":
        return "pending_review"
    if status in CLOSED_TASK_STATUSES:
        return "completed"
    if status in {"pending", "rework", "rework_required"}:
        return "rework_sent"
    return "approved"


def build_partner_payload(db: Session, request: Request, current_user, *, q: str = "") -> dict[str, Any]:
    roles = set(get_user_roles(db, current_user.id))
    tenant_id, branch_id = _active_tenant_branch(request, current_user, roles)
    financial_year = _active_financial_year(request)
    today = date.today()

    task_stmt = (
        select(ClientServiceTaskInstance)
        .options(
            selectinload(ClientServiceTaskInstance.client),
            selectinload(ClientServiceTaskInstance.catalogue),
            selectinload(ClientServiceTaskInstance.subscription).selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceTaskInstance.subscription).selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceTaskInstance.subscription).selectinload(ClientServiceSubscription.assigned_staff),
            selectinload(ClientServiceTaskInstance.assigned_to),
            selectinload(ClientServiceTaskInstance.comments).selectinload(ServiceTaskComment.created_by),
        )
    )
    task_stmt = _task_scope_filter(task_stmt, tenant_id, branch_id, current_user, roles, financial_year=financial_year)
    if q.strip():
        like = f"%{q.strip()}%"
        task_stmt = task_stmt.where(
            or_(
                ClientServiceTaskInstance.task_name.ilike(like),
                ClientServiceTaskInstance.description.ilike(like),
                ClientServiceTaskInstance.client.has(or_(Client.client_name.ilike(like), Client.client_code.ilike(like))),
                ClientServiceTaskInstance.catalogue.has(or_(ServiceCatalogue.service_name.ilike(like), ServiceCatalogue.service_code.ilike(like))),
            )
        )
    tasks = db.execute(
        task_stmt.order_by(
            ClientServiceTaskInstance.internal_target_date.is_(None),
            ClientServiceTaskInstance.internal_target_date.asc(),
            ClientServiceTaskInstance.priority.desc(),
            ClientServiceTaskInstance.id.desc(),
        )
    ).scalars().all()

    columns = [
        {"code": "pending_review", "label": "Pending Review", "hint": "Completed tasks waiting for partner review", "tasks": []},
        {"code": "clarification_required", "label": "Clarification Required", "hint": "Blocked tasks needing partner attention", "tasks": []},
        {"code": "rework_sent", "label": "Rework Sent", "hint": "Pending/reopened after review notes", "tasks": []},
        {"code": "approved", "label": "In Progress", "hint": "Work currently moving with the team", "tasks": []},
        {"code": "completed", "label": "Completed", "hint": "Closed tasks", "tasks": []},
    ]
    lookup = {c["code"]: c for c in columns}
    summary = {"total": len(tasks), "pending_review": 0, "blocked": 0, "overdue": 0, "due_today": 0, "completed": 0, "clients": set(), "engagements": set()}

    for task in tasks:
        _decorate_task(task, today)
        status = (task.status or "pending").lower()
        if getattr(task, "client_id", None):
            summary["clients"].add(task.client_id)
        if getattr(task, "subscription_id", None):
            summary["engagements"].add(task.subscription_id)
        if status == "completed":
            summary["pending_review"] += 1
        if status == "blocked":
            summary["blocked"] += 1
        if getattr(task, "is_overdue", False):
            summary["overdue"] += 1
        if getattr(task, "is_due_today", False):
            summary["due_today"] += 1
        if status in CLOSED_TASK_STATUSES:
            summary["completed"] += 1
        lookup[_task_bucket(task)]["tasks"].append(task)

    summary["clients"] = len(summary["clients"])
    summary["engagements"] = len(summary["engagements"])
    for col in columns:
        col["count"] = len(col["tasks"])

    engagement_stmt = (
        select(ClientServiceSubscription)
        .options(
            selectinload(ClientServiceSubscription.client),
            selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceSubscription.assigned_staff),
        )
    )
    engagement_stmt = _subscription_scope_filter(engagement_stmt, tenant_id, branch_id, current_user, roles, financial_year=financial_year)
    engagements = db.execute(
        engagement_stmt.order_by(ClientServiceSubscription.current_due_date.is_(None), ClientServiceSubscription.current_due_date.asc(), ClientServiceSubscription.id.desc()).limit(25)
    ).scalars().all()

    task_counts_by_subscription: dict[int, dict[str, int]] = defaultdict(lambda: {"total": 0, "completed": 0, "open": 0})
    for task in tasks:
        bucket = task_counts_by_subscription[int(task.subscription_id)]
        bucket["total"] += 1
        if (task.status or "").lower() in CLOSED_TASK_STATUSES:
            bucket["completed"] += 1
        else:
            bucket["open"] += 1

    for engagement in engagements:
        engagement.display_label = _engagement_label(engagement)
        engagement.client_display = getattr(getattr(engagement, "client", None), "client_name", None) or "Unlinked Client"
        engagement.task_counts = task_counts_by_subscription.get(int(engagement.id), {"total": 0, "completed": 0, "open": 0})
        due = getattr(engagement, "current_due_date", None)
        engagement.is_overdue = bool(due and due < today and (engagement.status or "").lower() not in {"completed", "closed", "locked"})

    return {"summary": summary, "columns": columns, "engagements": engagements, "q": q, "today": today, "financial_year": financial_year}


def _get_partner_task_or_redirect(db: Session, request: Request, current_user, task_id: int) -> ClientServiceTaskInstance | None:
    roles = set(get_user_roles(db, current_user.id))
    tenant_id, branch_id = _active_tenant_branch(request, current_user, roles)
    financial_year = _active_financial_year(request)
    stmt = select(ClientServiceTaskInstance).options(selectinload(ClientServiceTaskInstance.subscription)).where(ClientServiceTaskInstance.id == int(task_id))
    stmt = _task_scope_filter(stmt, tenant_id, branch_id, current_user, roles, financial_year=financial_year)
    return db.execute(stmt).scalar_one_or_none()


@router.get("/dashboard")
def partner_dashboard(request: Request, q: str = ""):
    db = CommonSessionLocal()
    try:
        current_user = get_current_user(request, db=db)
        if not current_user:
            return _redirect_login()
        if not _is_partner_user(db, current_user):
            return _redirect_denied()
        payload = build_partner_payload(db, request, current_user, q=q)
        return _render(request, "modules/partners/templates/partners/dashboard.html", db, current_user, title="Partner Workspace", payload=payload, q=q, errors=[])
    finally:
        db.close()


@router.get("/reviews")
def partner_review_board(request: Request, q: str = ""):
    db = CommonSessionLocal()
    try:
        current_user = get_current_user(request, db=db)
        if not current_user:
            return _redirect_login()
        if not _is_partner_user(db, current_user):
            return _redirect_denied()
        payload = build_partner_payload(db, request, current_user, q=q)
        return _render(request, "modules/partners/templates/partners/review_board.html", db, current_user, title="Partner Review Board", payload=payload, q=q, errors=[])
    finally:
        db.close()


@router.get("/clients")
def partner_clients(request: Request, q: str = ""):
    db = CommonSessionLocal()
    try:
        current_user = get_current_user(request, db=db)
        if not current_user:
            return _redirect_login()
        if not _is_partner_user(db, current_user):
            return _redirect_denied()
        payload = build_partner_payload(db, request, current_user, q=q)
        return _render(request, "modules/partners/templates/partners/clients.html", db, current_user, title="My Client Portfolio", payload=payload, q=q, errors=[])
    finally:
        db.close()


@router.get("/engagements/{engagement_id}")
def partner_engagement_detail(request: Request, engagement_id: int):
    db = CommonSessionLocal()
    try:
        current_user = get_current_user(request, db=db)
        if not current_user:
            return _redirect_login()
        if not _is_partner_user(db, current_user):
            return _redirect_denied()
        roles = set(get_user_roles(db, current_user.id))
        tenant_id, branch_id = _active_tenant_branch(request, current_user, roles)
        financial_year = _active_financial_year(request)
        engagement_stmt = select(ClientServiceSubscription).options(
            selectinload(ClientServiceSubscription.client),
            selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceSubscription.assigned_manager),
            selectinload(ClientServiceSubscription.assigned_staff),
        ).where(ClientServiceSubscription.id == int(engagement_id))
        engagement_stmt = _subscription_scope_filter(engagement_stmt, tenant_id, branch_id, current_user, roles, financial_year=financial_year)
        engagement = db.execute(engagement_stmt).scalar_one_or_none()
        if not engagement:
            return _redirect_denied()

        task_stmt = select(ClientServiceTaskInstance).options(
            selectinload(ClientServiceTaskInstance.client),
            selectinload(ClientServiceTaskInstance.catalogue),
            selectinload(ClientServiceTaskInstance.subscription).selectinload(ClientServiceSubscription.catalogue),
            selectinload(ClientServiceTaskInstance.assigned_to),
            selectinload(ClientServiceTaskInstance.comments).selectinload(ServiceTaskComment.created_by),
        ).where(ClientServiceTaskInstance.subscription_id == engagement.id, ClientServiceTaskInstance.is_active.is_(True))
        tasks = db.execute(task_stmt.order_by(ClientServiceTaskInstance.sequence_no.asc(), ClientServiceTaskInstance.id.asc())).scalars().all()
        today = date.today()
        for task in tasks:
            _decorate_task(task, today)
        documents = db.execute(
            select(EngagementDocument)
            .where(EngagementDocument.engagement_id == engagement.id, EngagementDocument.is_deleted.is_(False))
            .order_by(EngagementDocument.updated_at_utc.desc(), EngagementDocument.id.desc())
        ).scalars().all()
        engagement.display_label = _engagement_label(engagement)
        return _render(request, "modules/partners/templates/partners/engagement_detail.html", db, current_user, title="Partner Engagement Review", engagement=engagement, tasks=tasks, documents=documents, errors=[], financial_year=financial_year)
    finally:
        db.close()


@router.post("/tasks/{task_id}/review")
def partner_review_task(request: Request, task_id: int, action: str = Form(...), message: str = Form(""), csrf_token: str = Form(...)):
    db = CommonSessionLocal()
    try:
        validate_csrf(request, csrf_token)
        current_user = get_current_user(request, db=db)
        if not current_user:
            return _redirect_login()
        if not _is_partner_user(db, current_user):
            return _redirect_denied()
        task = _get_partner_task_or_redirect(db, request, current_user, task_id)
        if not task:
            return _redirect_denied()
        new_status, comment_type, visibility = REVIEW_ACTIONS.get(action, REVIEW_ACTIONS["approve"])
        task.status = new_status
        task.updated_by_user_id = current_user.id
        task.updated_at_utc = datetime.now(timezone.utc)
        note = (message or "").strip()
        if not note:
            note = {
                "approve": "Approved by partner.",
                "send_rework": "Sent back for rework by partner.",
                "clarification": "Clarification requested by partner.",
            }.get(action, "Partner review updated.")
        db.add(ServiceTaskComment(
            tenant_id=task.tenant_id,
            branch_id=task.branch_id,
            subscription_id=task.subscription_id,
            task_instance_id=task.id,
            comment_type=comment_type,
            visibility=visibility,
            message=note,
            created_by_user_id=current_user.id,
        ))
        db.commit()
        return RedirectResponse(url=f"/partner/engagements/{task.subscription_id}", status_code=303)
    finally:
        db.close()
