from __future__ import annotations

from datetime import datetime, timezone
from pathlib import Path
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

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

from app.core.db.common import CommonSessionLocal
from app.core.http_responses import ui_access_denied, not_found_response
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 .parsers.registry import BANK_OPTIONS
from .service import (
    can_use,
    create_job_folder,
    delete_owned_job,
    enqueue_job,
    ensure_worker_started,
    get_owned_job,
    get_owned_original_statement,
    job_view,
    list_user_jobs,
    save_uploads,
)

router = APIRouter(prefix="/tools/bank-statement-analyzer", tags=["bank-statement-analyzer-ui"])


def _display_timezone_name(request: Request, db, user) -> str:
    """Resolve the active branch timezone, falling back to tenant and India defaults."""
    from app.modules.core.tenancy.models import Branch, Tenant

    branch_id = request.session.get("active_branch_id") or getattr(user, "branch_id", None)
    tenant_id = request.session.get("active_tenant_id") or getattr(user, "tenant_id", None)

    if branch_id not in (None, "", 0, "0"):
        branch = db.scalar(select(Branch).where(Branch.id == int(branch_id)))
        branch_timezone = (getattr(branch, "timezone", None) or "").strip() if branch else ""
        if branch_timezone:
            return branch_timezone

    if tenant_id not in (None, "", 0, "0"):
        tenant = db.scalar(select(Tenant).where(Tenant.id == int(tenant_id)))
        tenant_timezone = (getattr(tenant, "default_timezone", None) or "").strip() if tenant else ""
        if tenant_timezone:
            return tenant_timezone

    return "Asia/Kolkata"


def _format_local_datetime(value: datetime | None, timezone_name: str) -> str | None:
    if value is None:
        return None
    try:
        target_timezone = ZoneInfo(timezone_name)
    except ZoneInfoNotFoundError:
        target_timezone = ZoneInfo("Asia/Kolkata")
    aware_value = value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc)
    return aware_value.astimezone(target_timezone).strftime("%d-%b-%Y %I:%M %p %Z")


def _localised_job_view(job, timezone_name: str) -> dict:
    view = job_view(job)
    view["submitted_at"] = _format_local_datetime(view.get("submitted_at"), timezone_name)
    view["completed_at"] = _format_local_datetime(view.get("completed_at"), timezone_name)
    view["expires_at"] = _format_local_datetime(view.get("expires_at"), timezone_name)
    return view


def _ctx(request, db, user, **extra):
    data = {
        "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),
        "title": "Bank Statement Analyzer",
        "bank_options": BANK_OPTIONS,
        "selected_bank": "auto",
        "financial_year": "",
        "classification_enabled": True,
    }
    data.update(extra)
    return data


def _auth(request, db):
    user = get_current_user(request, db=db)
    if not user:
        return None, None, RedirectResponse("/login", status_code=303)
    roles = get_user_roles(db, user.id)
    if not can_use(roles):
        return user, roles, ui_access_denied("Bank Statement Analyzer is available only to Partner, Manager, Staff and Consultant roles.")
    return user, roles, None


@router.get("")
def index(request: Request, job: str | None = None):
    ensure_worker_started()
    db = CommonSessionLocal()
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return denied
        timezone_name = _display_timezone_name(request, db, user)
        selected_job = get_owned_job(user.id, job) if job else None
        recent = [_localised_job_view(item, timezone_name) for item in list_user_jobs(user.id, limit=8)]
        active_job = _localised_job_view(selected_job, timezone_name) if selected_job else None
        return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html", _ctx(request, db, user, error="", active_job=active_job, recent_jobs=recent, display_timezone=timezone_name))
    finally:
        db.close()


@router.post("/analyze")
async def analyze(request: Request, csrf_token: str = Form(...), bank_selection: str = Form("auto"), financial_year: str = Form(""), customer_name: str = Form(""), account_number: str = Form(""), enable_classification: str | None = Form(None), statements: list[UploadFile] = File(...)):
    db = CommonSessionLocal()
    selected_bank = bank_selection if bank_selection in dict(BANK_OPTIONS) else "auto"
    classification_enabled = enable_classification == "1"
    job_dir: Path | None = None
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return denied
        validate_csrf(request, csrf_token)
        job_id, input_dir, _output_dir = create_job_folder(user, roles)
        job_dir = input_dir.parent
        paths = await save_uploads(statements, input_dir)
        enqueue_job(user=user, roles=roles, job_id=job_id, paths=paths, job_dir=job_dir, bank_selection=selected_bank, financial_year=financial_year, customer_override=customer_name, account_override=account_number, classification_enabled=classification_enabled)
        return RedirectResponse(f"/tools/bank-statement-analyzer?job={job_id}#analysis-status", status_code=303)
    except Exception as exc:
        if job_dir:
            import shutil
            shutil.rmtree(job_dir, ignore_errors=True)
        user = get_current_user(request, db=db)
        if not user:
            return RedirectResponse("/login", status_code=303)
        timezone_name = _display_timezone_name(request, db, user)
        recent = [_localised_job_view(item, timezone_name) for item in list_user_jobs(user.id, limit=8)]
        return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html", _ctx(request, db, user, error=str(exc), active_job=None, recent_jobs=recent, selected_bank=selected_bank, financial_year=financial_year, classification_enabled=classification_enabled, display_timezone=timezone_name), status_code=400)
    finally:
        db.close()


@router.get("/jobs")
def jobs(request: Request):
    ensure_worker_started()
    db = CommonSessionLocal()
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return denied
        timezone_name = _display_timezone_name(request, db, user)
        items = [_localised_job_view(item, timezone_name) for item in list_user_jobs(user.id, limit=100)]
        return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/jobs.html", _ctx(request, db, user, jobs=items, display_timezone=timezone_name))
    finally:
        db.close()


@router.get("/jobs/{job_id}/status")
def status(job_id: str, request: Request):
    ensure_worker_started()
    db = CommonSessionLocal()
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return JSONResponse({"detail": "Access denied"}, status_code=403)
        job = get_owned_job(user.id, job_id)
        if not job:
            return JSONResponse({"detail": "Analysis job not found"}, status_code=404)
        view = job_view(job)
        return JSONResponse({"id": view["id"], "status": view["status"], "progress_percent": view["progress_percent"], "queue_position": view["queue_position"], "estimated_wait": view["estimated_wait"], "download_ready": view["download_ready"], "error_message": view["error_message"]})
    finally:
        db.close()


@router.get("/jobs/{job_id}/download")
def download(job_id: str, request: Request):
    db = CommonSessionLocal()
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return denied
        job = get_owned_job(user.id, job_id)
        if not job:
            return not_found_response(request, "Analysis job not found or expired.")
        view = job_view(job)
        if not view["download_ready"]:
            return not_found_response(request, "Analysis workbook is not ready or has expired.")
        return FileResponse(path=job.output_file, filename=Path(job.output_file).name, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    finally:
        db.close()


@router.get("/jobs/{job_id}/statements/{file_index}/download")
def download_original_statement(job_id: str, file_index: int, request: Request):
    db = CommonSessionLocal()
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return denied
        resolved = get_owned_original_statement(user.id, job_id, file_index)
        if not resolved:
            return not_found_response(request, "Original statement is unavailable or has expired.")
        _job, path = resolved
        return FileResponse(
            path=path,
            filename=path.name,
            media_type="application/pdf",
        )
    finally:
        db.close()


@router.post("/jobs/{job_id}/delete")
def delete(job_id: str, request: Request, csrf_token: str = Form(...)):
    db = CommonSessionLocal()
    try:
        user, roles, denied = _auth(request, db)
        if denied:
            return denied
        validate_csrf(request, csrf_token)
        delete_owned_job(user.id, job_id)
        return RedirectResponse("/tools/bank-statement-analyzer/jobs", status_code=303)
    finally:
        db.close()
