from __future__ import annotations

from urllib.parse import quote

from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import RedirectResponse, StreamingResponse
from sqlalchemy import select

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.core.rbac.permission_guard import require_permission
from app.modules.notice_cases.service import (
    CASE_DOCUMENT_TYPES,
    CASE_PRIORITIES,
    CASE_STATUSES,
    CASE_TYPES,
    DEPARTMENTS,
    case_dashboard_summary,
    EVENT_TYPES,
    HEARING_STATUSES,
    ORDER_TYPES,
    add_event,
    add_hearing,
    add_order,
    can_delete_notice_case_documents,
    can_manage_notice_cases,
    can_upload_notice_case_documents,
    case_document_absolute_path,
    create_case,
    get_case,
    list_assignable_users,
    list_cases,
    list_clients_for_case,
    list_engagements_for_client,
    save_case_document,
    notice_case_upload_policy,
    UploadValidationError,
    update_case,
    user_can_access_case,
)
from app.modules.notice_cases.models import NoticeCaseDocument
from app.modules.core.tenancy.models import FinancialYear
from app.modules.core.tenancy.year_control import redirect_if_financial_year_locked, is_row_financial_year_locked

router = APIRouter(prefix="/notice-cases", tags=["notice-cases-ui"])


def _csrf_rejected(request: Request):
    from app.core.http_responses import forbidden_response
    return forbidden_response(request, "CSRF validation failed")



def _base_ctx(request: Request, user, db, **ctx):
    base = {
        "request": request,
        "current_user": user,
        "current_user_roles": get_user_roles(db, user.id),
        "current_user_permissions": get_user_permissions(db, user.id),
        "csrf_token": get_or_create_csrf_token(request),
        "departments": DEPARTMENTS,
        "case_types": CASE_TYPES,
        "case_statuses": CASE_STATUSES,
        "case_priorities": CASE_PRIORITIES,
        "event_types": EVENT_TYPES,
        "hearing_statuses": HEARING_STATUSES,
        "order_types": ORDER_TYPES,
        "case_document_types": CASE_DOCUMENT_TYPES,
    }
    base.update(ctx)
    return base


def _render(request: Request, template_name: str, db, user, status_code: int = 200, **ctx):
    return templates.TemplateResponse(template_name, _base_ctx(request, user, db, **ctx), status_code=status_code)


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


def _require_user(request: Request, db, permission: str):
    user = get_current_user(request, db=db)
    if not user:
        return None, RedirectResponse(url="/login", status_code=303)
    try:
        require_permission(db, user, permission)
    except Exception:
        return user, _redirect_denied()
    return user, None


def _active_tenant_id(request: Request, user) -> int:
    return int(request.session.get("active_tenant_id") or request.session.get("selected_tenant_id") or request.session.get("tenant_id") or user.tenant_id)


def _active_branch_id(request: Request, user, db) -> int | None:
    value = request.session.get("active_branch_id")
    perms = set(get_user_permissions(db, user.id))
    if value in (None, "", 0, "0"):
        if "notice_cases.cross_branch" in perms or "notice_cases.cross_tenant" in perms:
            return None
        return int(getattr(user, "branch_id", 0) or 0) or None
    return int(value)


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 _active_assessment_year(db, tenant_id: int, financial_year: str | None) -> str | None:
    fy_code = (financial_year or "").strip()
    if not fy_code:
        return None
    row = db.execute(
        select(FinancialYear).where(
            FinancialYear.tenant_id == tenant_id,
            FinancialYear.year_code == fy_code,
        )
    ).scalar_one_or_none()
    return row.assessment_year if row else None


def _financial_year_options(db, tenant_id: int):
    return list(
        db.execute(
            select(FinancialYear)
            .where(FinancialYear.tenant_id == tenant_id)
            .order_by(FinancialYear.start_date.desc(), FinancialYear.year_code.desc())
        ).scalars()
    )


def _selected_financial_year(request: Request, query_financial_year: str | None) -> str | None:
    value = (query_financial_year or "").strip()
    if value.lower() == "all":
        return None
    return value or _active_financial_year(request)


def _case_access_allowed(request: Request, db, user, case) -> bool:
    return user_can_access_case(
        db,
        user,
        case,
        active_tenant_id=_active_tenant_id(request, user),
        active_branch_id=_active_branch_id(request, user, db),
        active_financial_year=_active_financial_year(request),
        active_assessment_year=_active_assessment_year(db, _active_tenant_id(request, user), _active_financial_year(request)),
    )


def _form_data(**kwargs):
    return {k: v for k, v in kwargs.items()}


@router.get("")
def cases_list(
    request: Request,
    q: str = "",
    department: str = "",
    status: str = "",
    financial_year: str = "",
    assessment_year: str = "",
    include_archived: bool = False,
):
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.view")
        if response:
            return response
        tenant_id = _active_tenant_id(request, user)
        branch_id = _active_branch_id(request, user, db)
        selected_fy = _selected_financial_year(request, financial_year)
        selected_ay = (assessment_year or "").strip() or None
        if selected_fy:
            selected_ay = None
        rows = list_cases(
            db,
            tenant_id=tenant_id,
            branch_id=branch_id,
            q=q,
            department=department,
            status=status,
            include_archived=include_archived,
            financial_year=selected_fy,
            assessment_year=selected_ay,
        )
        return _render(
            request,
            "modules/notice_cases/templates/notice_cases/list.html",
            db,
            user,
            title="Notices & Cases",
            rows=rows,
            summary=case_dashboard_summary(rows),
            q=q,
            department=department,
            status=status,
            financial_year=selected_fy or "all",
            assessment_year=selected_ay or "",
            active_financial_year=_active_financial_year(request),
            financial_year_options=_financial_year_options(db, tenant_id),
            include_archived=include_archived,
            can_manage=can_manage_notice_cases(db, user),
        )
    finally:
        db.close()


@router.get("/new")
def case_create_page(request: Request, client_id: int | None = None):
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.create")
        if response:
            return response
        tenant_id = _active_tenant_id(request, user)
        branch_id = _active_branch_id(request, user, db)
        active_fy = _active_financial_year(request)
        active_ay = _active_assessment_year(db, tenant_id, active_fy)
        clients = list_clients_for_case(db, tenant_id=tenant_id, branch_id=branch_id)
        engagements = list_engagements_for_client(db, tenant_id=tenant_id, client_id=client_id, financial_year=active_fy) if client_id else []
        users = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id)
        return _render(request, "modules/notice_cases/templates/notice_cases/form.html", db, user, title="Create Notice / Case", mode="create", case=None, clients=clients, engagements=engagements, assignable_users=users, selected_client_id=client_id, form_error="", active_financial_year=active_fy, active_assessment_year=active_ay)
    finally:
        db.close()


@router.post("/new")
def case_create_submit(
    request: Request,
    client_id: int = Form(...),
    engagement_id: str = Form(""),
    department: str = Form("GST"),
    case_type: str = Form("Notice"),
    title: str = Form(""),
    reference_no: str = Form(""),
    din_ack_no: str = Form(""),
    notice_date: str = Form(""),
    due_date: str = Form(""),
    financial_year: str = Form(""),
    assessment_year: str = Form(""),
    period_label: str = Form(""),
    status: str = Form("open"),
    priority: str = Form("normal"),
    assigned_partner_user_id: str = Form(""),
    assigned_manager_user_id: str = Form(""),
    assigned_staff_user_id: str = Form(""),
    issue_summary: str = Form(""),
    remarks: str = Form(""),
    csrf_token: str = Form(...),
):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.create")
        if response:
            return response
        tenant_id = _active_tenant_id(request, user)
        branch_id = _active_branch_id(request, user, db)
        active_fy = _active_financial_year(request)
        active_ay = _active_assessment_year(db, tenant_id, active_fy)
        locked_response = redirect_if_financial_year_locked(db, tenant_id=tenant_id, year_code=active_fy, redirect_url=f"/notice-cases?financial_year={active_fy or ''}")
        if locked_response:
            return locked_response
        data = locals().copy(); data.pop("request", None); data.pop("db", None); data.pop("user", None); data.pop("response", None); data.pop("csrf_token", None); data.pop("tenant_id", None); data.pop("branch_id", None)
        data["active_financial_year"] = active_fy
        data["active_assessment_year"] = active_ay
        try:
            row = create_case(db, tenant_id=tenant_id, branch_id=branch_id, user=user, data=data)
            db.commit()
            return RedirectResponse(url=f"/notice-cases/{row.id}?created=1", status_code=303)
        except Exception as exc:
            db.rollback()
            clients = list_clients_for_case(db, tenant_id=tenant_id, branch_id=branch_id)
            engagements = list_engagements_for_client(db, tenant_id=tenant_id, client_id=client_id, financial_year=active_fy) if client_id else []
            users = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id)
            return _render(request, "modules/notice_cases/templates/notice_cases/form.html", db, user, title="Create Notice / Case", mode="create", case=data, clients=clients, engagements=engagements, assignable_users=users, selected_client_id=client_id, form_error=str(exc), active_financial_year=active_fy, active_assessment_year=active_ay)
    finally:
        db.close()


@router.get("/{case_id}")
def case_detail(request: Request, case_id: int):
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.view")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        upload_policy = notice_case_upload_policy(db, branch_id=case.branch_id)
        return _render(
            request,
            "modules/notice_cases/templates/notice_cases/detail.html",
            db,
            user,
            title=case.case_code,
            case=case,
            can_manage=can_manage_notice_cases(db, user),
            can_upload=can_upload_notice_case_documents(db, user),
            can_delete_documents=can_delete_notice_case_documents(db, user),
            upload_policy=upload_policy,
        )
    finally:
        db.close()


@router.get("/{case_id}/edit")
def case_edit_page(request: Request, case_id: int):
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.edit")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        clients = [case.client]
        active_fy = _active_financial_year(request)
        active_ay = _active_assessment_year(db, case.tenant_id, active_fy)
        engagements = list_engagements_for_client(db, tenant_id=case.tenant_id, client_id=case.client_id, financial_year=active_fy)
        users = list_assignable_users(db, tenant_id=case.tenant_id, branch_id=case.branch_id)
        return _render(request, "modules/notice_cases/templates/notice_cases/form.html", db, user, title="Edit Notice / Case", mode="edit", case=case, clients=clients, engagements=engagements, assignable_users=users, selected_client_id=case.client_id, form_error="", active_financial_year=active_fy, active_assessment_year=active_ay)
    finally:
        db.close()


@router.post("/{case_id}/edit")
def case_edit_submit(request: Request, case_id: int, department: str = Form("GST"), case_type: str = Form("Notice"), title: str = Form(""), reference_no: str = Form(""), din_ack_no: str = Form(""), notice_date: str = Form(""), due_date: str = Form(""), financial_year: str = Form(""), assessment_year: str = Form(""), period_label: str = Form(""), status: str = Form("open"), priority: str = Form("normal"), assigned_partner_user_id: str = Form(""), assigned_manager_user_id: str = Form(""), assigned_staff_user_id: str = Form(""), issue_summary: str = Form(""), remarks: str = Form(""), csrf_token: str = Form(...)):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.edit")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        if is_row_financial_year_locked(db, case):
            return RedirectResponse(url=f"/notice-cases/{case.id}?year_locked=1", status_code=303)
        active_fy = _active_financial_year(request)
        active_ay = _active_assessment_year(db, case.tenant_id, active_fy)
        data = locals().copy(); [data.pop(k, None) for k in ["request","db","user","response","case","csrf_token","case_id"]]
        data["active_financial_year"] = active_fy
        data["active_assessment_year"] = active_ay
        try:
            update_case(db, case=case, user=user, data=data)
            db.commit()
            return RedirectResponse(url=f"/notice-cases/{case.id}?updated=1", status_code=303)
        except Exception as exc:
            db.rollback()
            clients = [case.client]
            engagements = list_engagements_for_client(db, tenant_id=case.tenant_id, client_id=case.client_id, financial_year=active_fy)
            users = list_assignable_users(db, tenant_id=case.tenant_id, branch_id=case.branch_id)
            return _render(request, "modules/notice_cases/templates/notice_cases/form.html", db, user, title="Edit Notice / Case", mode="edit", case=case, clients=clients, engagements=engagements, assignable_users=users, selected_client_id=case.client_id, form_error=str(exc), active_financial_year=active_fy, active_assessment_year=active_ay)
    finally:
        db.close()


@router.post("/{case_id}/events")
def case_add_event(request: Request, case_id: int, event_type: str = Form("Internal Note"), event_date: str = Form(""), description: str = Form(""), next_due_date: str = Form(""), csrf_token: str = Form(...)):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.events.manage")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        if is_row_financial_year_locked(db, case):
            return RedirectResponse(url=f"/notice-cases/{case.id}?year_locked=1", status_code=303)
        add_event(db, case=case, user=user, data=locals())
        db.commit()
        return RedirectResponse(url=f"/notice-cases/{case_id}?event_added=1", status_code=303)
    finally:
        db.close()


@router.post("/{case_id}/hearings")
def case_add_hearing(request: Request, case_id: int, hearing_date: str = Form(""), hearing_time: str = Form(""), venue_or_mode: str = Form(""), officer_name: str = Form(""), agenda: str = Form(""), outcome: str = Form(""), status: str = Form("scheduled"), csrf_token: str = Form(...)):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.hearings.manage")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        if is_row_financial_year_locked(db, case):
            return RedirectResponse(url=f"/notice-cases/{case.id}?year_locked=1", status_code=303)
        add_hearing(db, case=case, user=user, data=locals())
        db.commit()
        return RedirectResponse(url=f"/notice-cases/{case_id}?hearing_added=1", status_code=303)
    finally:
        db.close()


@router.post("/{case_id}/orders")
def case_add_order(request: Request, case_id: int, order_type: str = Form("Other"), order_no: str = Form(""), order_date: str = Form(""), demand_amount: str = Form("0"), tax_amount: str = Form("0"), interest_amount: str = Form("0"), penalty_amount: str = Form("0"), summary: str = Form(""), appeal_due_date: str = Form(""), appeal_filed: str | None = Form(None), csrf_token: str = Form(...)):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.orders.manage")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        if is_row_financial_year_locked(db, case):
            return RedirectResponse(url=f"/notice-cases/{case.id}?year_locked=1", status_code=303)
        add_order(db, case=case, user=user, data=locals())
        db.commit()
        return RedirectResponse(url=f"/notice-cases/{case_id}?order_added=1", status_code=303)
    finally:
        db.close()


@router.post("/{case_id}/documents/upload")
def case_document_upload(request: Request, case_id: int, title: str = Form(""), document_type: str = Form("OTHER"), description: str = Form(""), event_id: str = Form(""), file: UploadFile = File(...), csrf_token: str = Form(...)):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.documents.upload")
        if response:
            return response
        case = get_case(db, case_id)
        if not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        if is_row_financial_year_locked(db, case):
            return RedirectResponse(url=f"/notice-cases/{case.id}?year_locked=1", status_code=303)
        if not file or not file.filename:
            return RedirectResponse(url=f"/notice-cases/{case_id}?error=missing_file", status_code=303)
        try:
            save_case_document(db, case=case, upload_file=file, user=user, title=title, document_type=document_type, description=description, event_id=int(event_id) if event_id else None)
        except UploadValidationError as exc:
            db.rollback()
            upload_policy = notice_case_upload_policy(db, branch_id=case.branch_id)
            return _render(
                request,
                "modules/notice_cases/templates/notice_cases/detail.html",
                db,
                user,
                status_code=400,
                title=case.case_code,
                case=case,
                can_manage=can_manage_notice_cases(db, user),
                can_upload=can_upload_notice_case_documents(db, user),
                can_delete_documents=can_delete_notice_case_documents(db, user),
                upload_policy=upload_policy,
                upload_error=str(exc),
            )
        db.commit()
        return RedirectResponse(url=f"/notice-cases/{case_id}?uploaded=1", status_code=303)
    finally:
        db.close()


@router.get("/documents/{document_id}/download")
def case_document_download(request: Request, document_id: int):
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.documents.download")
        if response:
            return response
        doc = db.execute(select(NoticeCaseDocument).where(NoticeCaseDocument.id == document_id, NoticeCaseDocument.is_deleted.is_(False))).scalar_one_or_none()
        case = get_case(db, doc.case_id) if doc else None
        if not doc or not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        path = case_document_absolute_path(doc)
        if not path.exists():
            return RedirectResponse(url=f"/notice-cases/{doc.case_id}?error=file_missing", status_code=303)
        quoted = quote(doc.original_filename)
        headers = {"Content-Disposition": f"attachment; filename*=UTF-8''{quoted}"}
        return StreamingResponse(path.open("rb"), media_type=doc.content_type or "application/octet-stream", headers=headers)
    finally:
        db.close()


@router.post("/documents/{document_id}/delete")
def case_document_delete(request: Request, document_id: int, csrf_token: str = Form(...)):
    try:
        validate_csrf(request, csrf_token)
    except PermissionError:
        return _csrf_rejected(request)
    db = CommonSessionLocal()
    try:
        user, response = _require_user(request, db, "notice_cases.documents.delete")
        if response:
            return response
        doc = db.execute(select(NoticeCaseDocument).where(NoticeCaseDocument.id == document_id, NoticeCaseDocument.is_deleted.is_(False))).scalar_one_or_none()
        case = get_case(db, doc.case_id) if doc else None
        if not doc or not case or not _case_access_allowed(request, db, user, case):
            return _redirect_denied()
        doc.is_deleted = True
        doc.deleted_at_utc = __import__('datetime').datetime.now(__import__('datetime').timezone.utc)
        doc.deleted_by_user_id = user.id
        db.commit()
        return RedirectResponse(url=f"/notice-cases/{doc.case_id}?deleted=1", status_code=303)
    finally:
        db.close()
