# routes/exit.py
from __future__ import annotations

import os
from datetime import datetime
from typing import Iterable

from flask import (
    Blueprint, render_template, request, redirect, url_for, flash
)
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename

from models import (
    db, ExitRequest, HandoverDoc, HandoverDocViewer, ExitEvent, Employee
)

exit_bp = Blueprint("exit", __name__, url_prefix="/exit")

# -----------------------------
# Small permission/utility stubs
# -----------------------------
def user_role():
    role_obj = getattr(current_user, "role", None)
    if not role_obj:
        return ""
    if hasattr(role_obj, "name"):   # adjust if your field is different
        return (role_obj.name or "").lower()
    return str(role_obj).lower()

def has_role(*roles):
    r = user_role()
    return r in [role.lower() for role in roles]

def require_hr() -> bool:
    # loosen/tighten as needed (e.g., admin can act as HR)
    return has_role("hr", "admin")

def require_manager() -> bool:
    return has_role("manager", "hod", "admin")

def require_it() -> bool:
    return has_role("manager", "admin")

def save_exit_file(file_storage, exit_id: int) -> str | None:
    """
    Saves uploaded file to static/exit_documents/<exit_id>/<filename>
    Returns a path relative to /static (so url_for('static', filename=...) works).
    """
    if not file_storage or not file_storage.filename:
        return None
    uploads_dir = os.path.join("static", "exit_documents", str(exit_id))
    os.makedirs(uploads_dir, exist_ok=True)
    filename = secure_filename(file_storage.filename)
    full_path = os.path.join(uploads_dir, filename)
    file_storage.save(full_path)
    # store relative path (inside /static)
    return os.path.join("exit_documents", str(exit_id), filename).replace("\\", "/")

# -----------------------------
# HR: create exit
# -----------------------------
@exit_bp.route("/new", methods=["GET", "POST"])
@login_required
def create_exit():
    if request.method == "POST":
        if not require_hr():
            flash("You do not have permission to initiate exits.", "danger")
            return redirect(url_for("exit.create_exit"))

        try:
            emp_id = int(request.form["employee_id"])
            resignation_date = datetime.strptime(request.form["resignation_date"], "%Y-%m-%d").date()
            proposed_lwd = datetime.strptime(request.form["proposed_lwd"], "%Y-%m-%d").date()
        except Exception:
            flash("Invalid form input.", "danger")
            return redirect(url_for("exit.create_exit"))

        er = ExitRequest(
            employee_id=emp_id,
            created_by=current_user.id,
            resignation_date=resignation_date,
            proposed_lwd=proposed_lwd,
            status="initiated",
            remarks=request.form.get("remarks") or None,
        )
        db.session.add(er)
        db.session.flush()
        db.session.add(ExitEvent(exit_id=er.id, actor_id=current_user.id, action="created"))
        db.session.commit()

        flash("Exit initiated.", "success")
        return redirect(url_for("exit.view", exit_id=er.id))

    # GET
    employees = Employee.query.filter_by(status="active").all()
    return render_template("exit/new.html", employees=employees)

# -----------------------------
# Exit detail page
# -----------------------------
@exit_bp.route("/<int:exit_id>")
@login_required
def view(exit_id):
    er = ExitRequest.query.get_or_404(exit_id)
    docs = HandoverDoc.query.filter_by(exit_id=exit_id).order_by(HandoverDoc.uploaded_at.asc()).all()

    exiting_emp_id = er.employee_id
    # Employees list for the “Share with viewers” select (excludes the exiting employee)
    
    employees = (Employee.query
                 .filter(Employee.status == "active", Employee.id != exiting_emp_id)
                 .order_by(Employee.full_name.asc())
                 .all())

    # --- Step state calculations ---
    has_docs = len(docs) > 0
    first_doc_time = docs[0].uploaded_at if has_docs else None

    # all viewers across all docs acknowledged?
    all_ack = True
    latest_ack_ts = None
    if has_docs:
        for d in docs:
            for v in d.viewers:  # relationship backref from HandoverDocViewer
                if v.can_view:
                    if v.acknowledged_at is None:
                        all_ack = False
                    else:
                        latest_ack_ts = max(latest_ack_ts or v.acknowledged_at, v.acknowledged_at)
    else:
        all_ack = False

    steps = [
        {"key": "manager_approved", "label": "Manager Accepted", "done": bool(er.manager_approved_at), "ts": er.manager_approved_at},
        {"key": "handover_uploaded", "label": "Handover Uploaded", "done": has_docs, "ts": first_doc_time},
        {"key": "all_ack", "label": "All Acknowledged", "done": all_ack, "ts": latest_ack_ts},
        {"key": "it_clear", "label": "IT Cleared", "done": bool(er.it_cleared_at), "ts": er.it_cleared_at},
        {"key": "mgr_final", "label": "Manager Final OK", "done": bool(er.final_ok_by_manager_at), "ts": er.final_ok_by_manager_at},
        {"key": "hr_final", "label": "HR Finalised", "done": er.status == "final_approved", "ts": er.final_ok_by_hr_at},
    ]

    # Determine current step (first not-done)
    current_idx = next((i for i, s in enumerate(steps) if not s["done"]), len(steps) - 1)

    return render_template(
        "exit/view.html",
        er=er,
        docs=docs,
        employees=employees,
        steps=steps,
        current_idx=current_idx
    )
# -----------------------------
# Manager/HOD approval of resignation
# -----------------------------
@exit_bp.post("/<int:exit_id>/manager-approve")
@login_required
def manager_approve(exit_id):
    if not require_manager():
        flash("Only Manager/HOD can approve.", "danger")
        return redirect(url_for("exit.view", exit_id=exit_id))

    er = ExitRequest.query.get_or_404(exit_id)

    # NEW: fetch the employee and move to notice
    emp = Employee.query.get_or_404(er.employee_id)

    er.manager_approved_at = datetime.utcnow()
    er.manager_approved_by = current_user.id
    er.manager_accepted = True  # (you already use this flag for payroll logic)
    er.status = "manager_approved"

    # NEW: put employee into notice period (do not set exit_date here)
    if emp.status != "resigned":
        emp.status = "notice"

    db.session.add(ExitEvent(exit_id=er.id, actor_id=current_user.id, action="manager_approved"))
    db.session.add(emp)  # make sure the status change is persisted
    db.session.commit()

    flash("Resignation accepted by Manager. Employee moved to notice period.", "success")
    return redirect(url_for("exit.view", exit_id=exit_id))
# (Optional) HOD approve — include only if you enforce HOD in your org
@exit_bp.post("/<int:exit_id>/hod-approve")
@login_required
def hod_approve(exit_id):
    if not require_manager():
        flash("Only HOD can approve.", "danger")
        return redirect(url_for("exit.view", exit_id=exit_id))

    er = ExitRequest.query.get_or_404(exit_id)
    er.hod_approved_at = datetime.utcnow()
    er.hod_approved_by = current_user.id

    db.session.add(ExitEvent(exit_id=er.id, actor_id=current_user.id, action="hod_approved"))
    db.session.commit()
    flash("HOD approved.", "success")
    return redirect(url_for("exit.view", exit_id=exit_id))

# -----------------------------
# Employee: upload handover + assign viewers
# -----------------------------
@exit_bp.post("/<int:exit_id>/handover/upload")
@login_required
def upload_handover(exit_id):
    er = ExitRequest.query.get_or_404(exit_id)

    f = request.files.get("file")
    title = request.form.get("title") or "Handover"
    viewer_ids = request.form.getlist("viewer_ids")  # list of employee ids (strings)

    rel_path = save_exit_file(f, exit_id)
    if not rel_path:
        flash("Please choose a file to upload.", "warning")
        return redirect(url_for("exit.view", exit_id=exit_id))

    doc = HandoverDoc(exit_id=exit_id, title=title, file_path=rel_path, uploaded_by=current_user.id)
    db.session.add(doc)
    db.session.flush()

    for vid in viewer_ids:
        try:
            db.session.add(HandoverDocViewer(doc_id=doc.id, viewer_emp_id=int(vid)))
        except Exception:
            continue

    # Move status forward if relevant
    er.status = "handover_in_progress"
    db.session.add(ExitEvent(exit_id=er.id, actor_id=current_user.id, action="uploaded_doc", note=title))
    db.session.commit()

    flash("Handover document uploaded and viewers assigned.", "success")
    return redirect(url_for("exit.view", exit_id=exit_id))

# -----------------------------
# Viewer: acknowledge handover
# -----------------------------
# routes/exit.py
@exit_bp.post("/handover/<int:doc_id>/ack")
@login_required
def ack_handover(doc_id):
    v = (HandoverDocViewer.query
         .filter_by(doc_id=doc_id, viewer_emp_id=current_user.id, can_view=True)  # <-- use current_user.id
         .first_or_404())
    v.acknowledged_at = datetime.utcnow()
    db.session.commit()

    # (optional) add an ExitEvent
    d = HandoverDoc.query.get(doc_id)
    if d:
        db.session.add(ExitEvent(exit_id=d.exit_id, actor_id=current_user.id, action="handover_ack"))
        db.session.commit()

    flash("✅ Handover acknowledged.", "success")
    return redirect(request.referrer or url_for("exit_list.acks_queue"))  # adjust blueprint name if different

# -----------------------------
# IT clearance
# -----------------------------
@exit_bp.post("/<int:exit_id>/it-clear")
@login_required
def it_clear(exit_id):
    if not require_it():
        flash("Only IT can mark clearance.", "danger")
        return redirect(url_for("exit.view", exit_id=exit_id))

    er = ExitRequest.query.get_or_404(exit_id)
    er.it_cleared_at = datetime.utcnow()
    er.it_clearance_by = current_user.id

    maybe_move_to_clearances(er)
    db.session.add(ExitEvent(exit_id=exit_id, actor_id=current_user.id, action="it_cleared"))
    db.session.commit()

    flash("IT clearance recorded.", "success")
    return redirect(url_for("exit.view", exit_id=exit_id))

# -----------------------------
# Manager final OK (post-handover)
# -----------------------------
@exit_bp.post("/<int:exit_id>/manager-final-ok")
@login_required
def manager_final_ok(exit_id):
    if not require_manager():
        flash("Only Manager/HOD can give final OK.", "danger")
        return redirect(url_for("exit.view", exit_id=exit_id))

    er = ExitRequest.query.get_or_404(exit_id)
    er.final_ok_by_manager_at = datetime.utcnow()

    maybe_move_to_clearances(er)
    db.session.add(ExitEvent(exit_id=exit_id, actor_id=current_user.id, action="manager_final_ok"))
    db.session.commit()

    flash("Manager final OK recorded.", "success")
    return redirect(url_for("exit.view", exit_id=exit_id))

# -----------------------------
# HR final approval (closes exit if all prerequisites satisfied)
# -----------------------------
@exit_bp.post("/<int:exit_id>/final-approve")
@login_required
def final_approve(exit_id):
    if not require_hr():
        flash("Only HR can finalise exit.", "danger")
        return redirect(url_for("exit.view", exit_id=exit_id))

    er = ExitRequest.query.get_or_404(exit_id)

    if not all_clear(er.id):
        flash("All clearances & acknowledgments are not complete yet.", "warning")
        return redirect(url_for("exit.view", exit_id=exit_id))

    # --- NEW: update employee record on HR finalise ---
    emp = Employee.query.get_or_404(er.employee_id)
    emp.status = "resigned"
    emp.exit_date = er.proposed_lwd  # if you store a revised LWD later, use that instead

    er.final_ok_by_hr_at = datetime.utcnow()
    er.status = "final_approved"

    db.session.add(ExitEvent(exit_id=exit_id, actor_id=current_user.id, action="final_approved"))
    db.session.add(emp)  # ensure status/exit_date change is persisted
    db.session.commit()

    flash("Exit finalised. Employee marked as 'resigned' with exit date set.", "success")
    return redirect(url_for("exit.view", exit_id=exit_id))
# -----------------------------
# My Acks (for viewers)
# -----------------------------
@exit_bp.get("/my-acks")
@login_required
def list_my_acks():
    my_emp_id = getattr(current_user, "employee_id", None)
    if not my_emp_id:
        flash("No employee mapping found for your user.", "warning")
        return redirect(url_for("exit.create_exit"))

    # (doc, exit, viewer) tuples
    rows = (
        db.session.query(HandoverDoc, ExitRequest, HandoverDocViewer)
        .join(ExitRequest, ExitRequest.id == HandoverDoc.exit_id)
        .join(HandoverDocViewer, HandoverDocViewer.doc_id == HandoverDoc.id)
        .filter(HandoverDocViewer.viewer_emp_id == my_emp_id)
        .order_by(HandoverDoc.uploaded_at.desc())
        .all()
    )

    data = [{"doc": d, "exit": er, "viewer": v} for (d, er, v) in rows]
    return render_template("exit/my_acks.html", rows=data)

# -----------------------------
# Helpers
# -----------------------------
def all_clear(exit_id: int) -> bool:
    er = ExitRequest.query.get(exit_id)
    if not er:
        return False

    # Must have manager approval, IT clearance, and manager final OK
    if not (er.manager_approved_at and er.it_cleared_at and er.final_ok_by_manager_at):
        return False

    # All viewers acknowledged for all docs
    docs = HandoverDoc.query.filter_by(exit_id=exit_id).all()
    for d in docs:
        pending = HandoverDocViewer.query.filter_by(
            doc_id=d.id, can_view=True, acknowledged_at=None
        ).count()
        if pending > 0:
            return False
    return True

def maybe_move_to_clearances(er: ExitRequest):
    """
    Move to 'clearances_pending' once manager approved and any handover exists.
    """
    has_docs = db.session.query(HandoverDoc.id).filter_by(exit_id=er.id).first() is not None
    if er.manager_approved_at and has_docs:
        er.status = "clearances_pending"