# routes/travel.py
from flask import Blueprint, render_template, request, redirect, url_for, flash, send_file, current_app, abort
from flask_login import login_required, current_user
from datetime import datetime, date
from io import StringIO, BytesIO
import csv
from utils.notify import notify_employee, notify_many
import mimetypes, os
from werkzeug.utils import secure_filename
from werkzeug.routing import BuildError

from extensions import db
from models import Employee, TRRequest, TRItem, TRApprovalLog, Role, Department, ReimbursementType

tr_bp = Blueprint('tr', __name__, url_prefix='/travel')

# ======================================
# Helpers
# ======================================

ALLOWED_EXTS = {'.pdf', '.png', '.jpg', '.jpeg', '.webp', '.doc', '.docx', '.xls', '.xlsx', '.csv'}
MAX_ITEM_UPLOAD_MB = 15


def _save_tr_item_attachment(file_storage, request_id, item_id):
    """Save upload; return (rel_path, original_name, mimetype)."""
    if not file_storage or not file_storage.filename:
        return None, None, None

    # size guard
    file_storage.stream.seek(0, os.SEEK_END)
    size = file_storage.stream.tell()
    file_storage.stream.seek(0)
    if size > MAX_ITEM_UPLOAD_MB * 1024 * 1024:
        raise ValueError(f"File exceeds {MAX_ITEM_UPLOAD_MB}MB")

    fname = secure_filename(file_storage.filename)
    ext = os.path.splitext(fname)[1].lower()
    if ext not in ALLOWED_EXTS:
        raise ValueError("Unsupported file type")

    root = current_app.config.get('UPLOAD_FOLDER') or os.path.join(current_app.root_path, 'uploads')
    full_dir = os.path.join(root, 'tr', str(request_id))
    os.makedirs(full_dir, exist_ok=True)

    saved_name = f"item_{item_id}_{fname}"
    full_path = os.path.join(full_dir, saved_name)
    file_storage.save(full_path)

    mime = mimetypes.guess_type(saved_name)[0] or 'application/octet-stream'
    rel_path = os.path.join('tr', str(request_id), saved_name).replace('\\', '/')
    return rel_path, fname, mime


def _hr_ids():
    """Find HR recipients via role or department name."""
    try:
        ids = [e.id for e in Employee.query.join(Role).filter(Role.name.ilike('hr')).all()]
        if ids:
            return ids
    except Exception:
        pass
    try:
        return [e.id for e in Employee.query.join(Department).filter(Department.name.ilike('%hr%')).all()]
    except Exception:
        return []


def _role_name(user) -> str:
    try:
        return (getattr(getattr(user, 'role', None), 'name', '') or '').strip().lower()
    except Exception:
        return ''


def _is_admin(user) -> bool:
    rn = _role_name(user)
    return rn in ('admin', 'system admin', 'superadmin', 'hr admin')


def _is_hr_or_admin(user) -> bool:
    rn = _role_name(user)
    return rn in ('hr', 'hr admin', 'admin', 'system admin', 'superadmin','payroll')


def _is_hr(user_id: int) -> bool:
    """Backward-compatible HR detector, but treat Admin as HR-equivalent."""
    if _is_hr_or_admin(current_user):
        return True
    return user_id in set(_hr_ids())


def _hod_team_ids(hod_id: int):
    """Team = employees whose reporting_manager_id == hod_id."""
    return [e.id for e in Employee.query.filter_by(reporting_manager_id=hod_id).all()]


def _rtype_name(tr: TRRequest) -> str:
    try:
        if tr.reimb_type:
            return tr.reimb_type.name
    except Exception:
        pass
    return "Reimbursement"


def _rtype_rules(rt: ReimbursementType):
    return {
        "requires_route": bool(getattr(rt, "requires_route_fields", False)),
        "requires_km": bool(getattr(rt, "requires_km_fields", False)),
    }


def _fmt_req_title(tr: TRRequest) -> str:
    return f"{_rtype_name(tr)} ({tr.from_date} to {tr.to_date})"


# ======================================
# Create / Edit TR Request
# ======================================

@tr_bp.route('/request/new', methods=['GET', 'POST'])
@login_required
def tr_new():
    today = date.today()
    reimb_types = (ReimbursementType.query
                   .filter_by(is_active=True)
                   .order_by(ReimbursementType.name.asc())
                   .all())

    if request.method == 'POST':
        # ✅ FK-based type
        reimb_type_id = request.form.get('reimb_type_id', type=int)
        rt = ReimbursementType.query.get(reimb_type_id) if reimb_type_id else None
        if not rt or not rt.is_active:
            flash("Reimbursement Type is required.", "danger")
            return redirect(url_for('tr.tr_new'))

        from_date_s = (request.form.get('from_date') or '').strip()
        to_date_s = (request.form.get('to_date') or '').strip()

        if not from_date_s or not to_date_s:
            flash("From Date and To Date are required.", "danger")
            return redirect(url_for('tr.tr_new'))

        try:
            from_d = datetime.strptime(from_date_s, '%Y-%m-%d').date()
            to_d = datetime.strptime(to_date_s, '%Y-%m-%d').date()
        except ValueError:
            flash("Invalid date format.", "danger")
            return redirect(url_for('tr.tr_new'))

        if to_d < from_d:
            flash("To Date cannot be before From Date.", "danger")
            return redirect(url_for('tr.tr_new'))

        rules = _rtype_rules(rt)

        # Create request (month/year derived for filtering/export)
        tr = TRRequest(
            employee_id=current_user.id,
            reimb_type_id=rt.id,
            from_date=from_d,
            to_date=to_d,
            month=from_d.month,
            year=from_d.year,
            status='pending_manager'
        )
        db.session.add(tr)
        db.session.flush()

        # Arrays
        modes = request.form.getlist('item_mode[]')
        froms = request.form.getlist('item_from[]')
        tos = request.form.getlist('item_to[]')
        kms = request.form.getlist('item_kms[]')
        rates = request.form.getlist('item_rate[]')
        amts = request.form.getlist('item_amount[]')
        notes = request.form.getlist('item_notes[]')

        files = request.files.getlist('item_attachment[]') or request.files.getlist('item_file[]')

        # Basic guard
        if not modes or all((m or '').strip() == '' for m in modes):
            flash("Add at least one line item.", "danger")
            db.session.rollback()
            return redirect(url_for('tr.tr_new'))

        total_kms, total_amt = 0.0, 0.0

        for i in range(len(modes)):
            mode = (modes[i] or '').strip()
            if not mode:
                flash(f"Line item {i+1}: Mode of transport is required.", "danger")
                db.session.rollback()
                return redirect(url_for('tr.tr_new'))

            # attachment mandatory
            f = files[i] if i < len(files) else None
            if not f or not f.filename:
                flash(f"Line item {i+1}: Attachment is required.", "danger")
                db.session.rollback()
                return redirect(url_for('tr.tr_new'))

            from_loc = (froms[i] or '').strip()
            to_loc = (tos[i] or '').strip()

            if rules["requires_route"]:
                if not from_loc or not to_loc:
                    flash(f"Line item {i+1}: From/To is required for {rt.name}.", "danger")
                    db.session.rollback()
                    return redirect(url_for('tr.tr_new'))

            k = float(kms[i] or 0)
            r = float(rates[i] or 0)

            if rules["requires_km"]:
                if k <= 0 or r <= 0:
                    flash(f"Line item {i+1}: KMs and Rate are required for {rt.name}.", "danger")
                    db.session.rollback()
                    return redirect(url_for('tr.tr_new'))
                # ✅ enforce computed amount
                amt_val = round(k * r, 2)
            else:
                amt_val = float(amts[i] or 0)

            if amt_val <= 0:
                flash(f"Line item {i+1}: Amount must be greater than 0.", "danger")
                db.session.rollback()
                return redirect(url_for('tr.tr_new'))

            it = TRItem(
                request_id=tr.id,
                trip_date=None,
                from_loc=from_loc or None,
                to_loc=to_loc or None,
                kms_total=k if k else None,
                rate=r if r else None,
                amount=amt_val,
                mode=mode,
                notes=(notes[i] or '').strip() or None,
            )
            db.session.add(it)
            db.session.flush()

            try:
                rel, oname, mime = _save_tr_item_attachment(f, tr.id, it.id)
                it.attachment_path = rel
                it.attachment_name = oname
                it.attachment_type = mime
            except ValueError as e:
                flash(f"Line item {i+1}: {e}", "danger")
                db.session.rollback()
                return redirect(url_for('tr.tr_new'))

            total_kms += float(k or 0)
            total_amt += float(amt_val or 0)

        tr.total_kms = round(total_kms, 2)
        tr.total_amount = round(total_amt, 2)
        db.session.commit()

        # Notifications
        notify_employee(
            employee_id=current_user.id,
            title="Reimbursement submitted",
            message=f"Your {_fmt_req_title(tr)} was submitted. Awaiting HOD approval.",
            link=url_for('tr.tr_view', req_id=tr.id, _external=True)
        )

        mgr_id = getattr(current_user, 'reporting_manager_id', None)
        if mgr_id:
            notify_employee(
                employee_id=mgr_id,
                title="Reimbursement approval needed",
                message=f"{current_user.full_name}'s {_fmt_req_title(tr)} awaits your approval.",
                link=url_for('tr.hod_pending', _external=True)
            )

        flash('✅ Reimbursement request submitted.', 'success')
        return redirect(url_for('tr.tr_my'))

    return render_template('tr/tr_new.html', today=today, reimb_types=reimb_types)


@tr_bp.route('/request/<int:req_id>/edit', methods=['GET', 'POST'])
@login_required
def tr_edit(req_id):
    tr = TRRequest.query.get_or_404(req_id)
    if tr.employee_id != current_user.id or tr.status not in ('pending_manager', 'rejected'):
        flash('⚠️ You can only edit your own pending/rejected request.', 'warning')
        return redirect(url_for('tr.tr_view', req_id=req_id))

    reimb_types = (ReimbursementType.query
                   .filter_by(is_active=True)
                   .order_by(ReimbursementType.name.asc())
                   .all())

    if request.method == 'POST':
        reimb_type_id = request.form.get('reimb_type_id', type=int)
        rt = ReimbursementType.query.get(reimb_type_id) if reimb_type_id else None
        if not rt or not rt.is_active:
            flash("Reimbursement Type is required.", "danger")
            return redirect(url_for('tr.tr_edit', req_id=tr.id))

        from_date_s = (request.form.get('from_date') or '').strip()
        to_date_s = (request.form.get('to_date') or '').strip()

        try:
            from_d = datetime.strptime(from_date_s, '%Y-%m-%d').date() if from_date_s else tr.from_date
            to_d = datetime.strptime(to_date_s, '%Y-%m-%d').date() if to_date_s else tr.to_date
        except ValueError:
            flash("Invalid date format.", "danger")
            return redirect(url_for('tr.tr_edit', req_id=tr.id))

        if from_d and to_d and to_d < from_d:
            flash("To Date cannot be before From Date.", "danger")
            return redirect(url_for('tr.tr_edit', req_id=tr.id))

        rules = _rtype_rules(rt)

        # Replace all items (but allow preserving attachment via hidden fields)
        TRItem.query.filter_by(request_id=tr.id).delete()

        modes = request.form.getlist('item_mode[]')
        froms = request.form.getlist('item_from[]')
        tos = request.form.getlist('item_to[]')
        kms = request.form.getlist('item_kms[]')
        rates = request.form.getlist('item_rate[]')
        amts = request.form.getlist('item_amount[]')
        notes = request.form.getlist('item_notes[]')

        existing_paths = request.form.getlist('existing_attachment_path[]')
        existing_names = request.form.getlist('existing_attachment_name[]')
        existing_types = request.form.getlist('existing_attachment_type[]')

        files = request.files.getlist('item_attachment[]') or request.files.getlist('item_file[]')

        total_kms, total_amt = 0.0, 0.0

        for i in range(len(modes)):
            mode = (modes[i] or '').strip()
            if not mode:
                flash(f"Line item {i+1}: Mode is required.", "danger")
                db.session.rollback()
                return redirect(url_for('tr.tr_edit', req_id=tr.id))

            from_loc = (froms[i] or '').strip()
            to_loc = (tos[i] or '').strip()

            if rules["requires_route"]:
                if not from_loc or not to_loc:
                    flash(f"Line item {i+1}: From/To is required for {rt.name}.", "danger")
                    db.session.rollback()
                    return redirect(url_for('tr.tr_edit', req_id=tr.id))

            k = float(kms[i] or 0)
            r = float(rates[i] or 0)

            if rules["requires_km"]:
                if k <= 0 or r <= 0:
                    flash(f"Line item {i+1}: KMs and Rate are required for {rt.name}.", "danger")
                    db.session.rollback()
                    return redirect(url_for('tr.tr_edit', req_id=tr.id))
                amt_val = round(k * r, 2)
            else:
                amt_val = float(amts[i] or 0)

            if amt_val <= 0:
                flash(f"Line item {i+1}: Amount must be > 0.", "danger")
                db.session.rollback()
                return redirect(url_for('tr.tr_edit', req_id=tr.id))

            it = TRItem(
                request_id=tr.id,
                trip_date=None,
                from_loc=from_loc or None,
                to_loc=to_loc or None,
                kms_total=k if k else None,
                rate=r if r else None,
                amount=amt_val,
                mode=mode,
                notes=(notes[i] or '').strip() or None,
            )
            db.session.add(it)
            db.session.flush()

            # Attachment: new upload OR preserve existing
            f = files[i] if i < len(files) else None
            if f and f.filename:
                try:
                    rel, oname, mime = _save_tr_item_attachment(f, tr.id, it.id)
                    it.attachment_path = rel
                    it.attachment_name = oname
                    it.attachment_type = mime
                except ValueError as e:
                    flash(f"Line item {i+1}: {e}", "danger")
                    db.session.rollback()
                    return redirect(url_for('tr.tr_edit', req_id=tr.id))
            else:
                p = existing_paths[i] if i < len(existing_paths) else ''
                n = existing_names[i] if i < len(existing_names) else ''
                t = existing_types[i] if i < len(existing_types) else ''
                if not p:
                    flash(f"Line item {i+1}: Attachment is required (upload or keep existing).", "danger")
                    db.session.rollback()
                    return redirect(url_for('tr.tr_edit', req_id=tr.id))
                it.attachment_path = p or None
                it.attachment_name = n or None
                it.attachment_type = t or None

            total_kms += float(k or 0)
            total_amt += float(amt_val or 0)

        tr.reimb_type_id = rt.id
        tr.from_date = from_d
        tr.to_date = to_d
        if from_d:
            tr.month = from_d.month
            tr.year = from_d.year

        tr.total_kms = round(total_kms, 2)
        tr.total_amount = round(total_amt, 2)

        db.session.commit()
        flash('✅ Request updated.', 'success')
        return redirect(url_for('tr.tr_view', req_id=tr.id))

    return render_template('tr/tr_edit.html', tr=tr, reimb_types=reimb_types)


@tr_bp.route('/request/<int:req_id>')
@login_required
def tr_view(req_id):
    tr = TRRequest.query.get_or_404(req_id)

    # --- authorization ---
    if not _is_hr_or_admin(current_user):
        team_ids = set(_hod_team_ids(current_user.id))
        if not (tr.employee_id == current_user.id or tr.employee_id in team_ids):
            flash('❌ Not authorized.', 'danger')
            return redirect(url_for('employee.dashboard'))

    # ✅ fetch logs here (ordered)
    logs = (TRApprovalLog.query
            .filter(TRApprovalLog.request_id == tr.id)
            .order_by(TRApprovalLog.acted_on.desc())
            .all())

    return render_template('tr/tr_view.html', tr=tr, logs=logs)


# ======================================
# HOD Inbox
# ======================================

@tr_bp.route('/hod/pending')
@login_required
def hod_pending():
    """
    HODs see their team. HR/Admin can open this view too.
    """
    if _is_hr_or_admin(current_user):
        base_q = TRRequest.query.order_by(TRRequest.created_on.desc())
        employees = Employee.query.order_by(Employee.full_name).all()
    else:
        team_ids = _hod_team_ids(current_user.id)
        base_q = (TRRequest.query
                  .filter(TRRequest.employee_id.in_(team_ids))
                  .order_by(TRRequest.created_on.desc()))
        employees = Employee.query.filter(Employee.id.in_(team_ids)).order_by(Employee.full_name).all()

    emp_id = request.args.get('employee_id', type=int)
    month = request.args.get('month', type=int)
    year = request.args.get('year', type=int)

    q = base_q
    if emp_id:
        q = q.filter(TRRequest.employee_id == emp_id)
    if month:
        q = q.filter(TRRequest.month == month)
    if year:
        q = q.filter(TRRequest.year == year)

    pending = q.filter(TRRequest.status == 'pending_manager').all()
    processed = q.filter(TRRequest.status.in_(('manager_approved', 'rejected'))).all()

    return render_template(
        'tr/hod_pending.html',
        pending=pending, processed=processed,
        employees=employees, emp_id=emp_id, month=month, year=year
    )


@tr_bp.route('/hod/approve/<int:req_id>', methods=['POST'])
@login_required
def hod_approve(req_id):
    tr = TRRequest.query.get_or_404(req_id)
    if tr.status != 'pending_manager':
        flash('⚠️ Already processed.', 'warning')
        return redirect(url_for('tr.hod_pending'))

    team_ids = _hod_team_ids(current_user.id)
    if tr.employee_id not in team_ids:
        flash('❌ Not authorized.', 'danger')
        return redirect(url_for('tr.hod_pending'))

    tr.status = 'manager_approved'
    db.session.add(TRApprovalLog(
        request_id=tr.id, actor_id=current_user.id, actor_role='hod',
        action='approved', remark=(request.form.get('remark') or '').strip()
    ))
    db.session.commit()

    notify_employee(
        tr.employee_id,
        "Reimbursement approved by HOD",
        f"Your {_fmt_req_title(tr)} was approved by HOD and sent to HR.",
        url_for('tr.tr_view', req_id=tr.id, _external=True)
    )

    flash('✅ Approved and sent to HR.', 'success')
    return redirect(url_for('tr.hod_pending'))


@tr_bp.route('/hod/reject/<int:req_id>', methods=['POST'])
@login_required
def hod_reject(req_id):
    tr = TRRequest.query.get_or_404(req_id)
    if tr.status != 'pending_manager':
        flash('⚠️ Already processed.', 'warning')
        return redirect(url_for('tr.hod_pending'))

    team_ids = _hod_team_ids(current_user.id)
    if tr.employee_id not in team_ids:
        flash('❌ Not authorized.', 'danger')
        return redirect(url_for('tr.hod_pending'))

    tr.status = 'rejected'
    db.session.add(TRApprovalLog(
        request_id=tr.id, actor_id=current_user.id, actor_role='hod',
        action='rejected', remark=(request.form.get('remark') or '').strip()
    ))
    db.session.commit()

    notify_employee(
        tr.employee_id,
        "Reimbursement rejected by HOD",
        f"Your {_fmt_req_title(tr)} was rejected.",
        url_for('tr.tr_view', req_id=tr.id, _external=True)
    )

    flash('❌ Rejected.', 'danger')
    return redirect(url_for('tr.hod_pending'))


# ======================================
# HR Inbox + Disbursement (HR/Admin)
# ======================================

@tr_bp.route('/hr/pending')
@login_required
def hr_pending():
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    q = TRRequest.query.order_by(TRRequest.created_on.desc())

    emp_id = request.args.get('employee_id', type=int)
    month = request.args.get('month', type=int)
    year = request.args.get('year', type=int)

    if emp_id:
        q = q.filter(TRRequest.employee_id == emp_id)
    if month:
        q = q.filter(TRRequest.month == month)
    if year:
        q = q.filter(TRRequest.year == year)

    pending = q.filter(TRRequest.status == 'manager_approved').all()
    processed = q.filter(TRRequest.status.in_(('hr_approved', 'rejected', 'paid'))).all()

    employees = Employee.query.order_by(Employee.full_name).all()
    return render_template(
        'tr/hr_pending.html',
        pending=pending, processed=processed,
        employees=employees, emp_id=emp_id, month=month, year=year
    )


@tr_bp.route('/hr/approve/<int:req_id>', methods=['POST'])
@login_required
def hr_approve(req_id):
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    tr = TRRequest.query.get_or_404(req_id)
    if tr.status != 'manager_approved':
        flash('⚠️ Not eligible.', 'warning')
        return redirect(url_for('tr.hr_pending'))

    tr.status = 'hr_approved'
    db.session.add(TRApprovalLog(
        request_id=tr.id, actor_id=current_user.id, actor_role='hr',
        action='approved', remark=(request.form.get('remark') or '').strip()
    ))
    db.session.commit()

    notify_employee(
        tr.employee_id,
        "Reimbursement approved by HR",
        f"Your {_fmt_req_title(tr)} was HR approved.",
        url_for('tr.tr_view', req_id=tr.id, _external=True)
    )

    flash('✅ HR approved.', 'success')
    return redirect(url_for('tr.hr_pending'))


@tr_bp.route('/hr/mark-paid/<int:req_id>', methods=['POST'])
@login_required
def hr_mark_paid(req_id):
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    tr = TRRequest.query.get_or_404(req_id)
    if tr.status != 'hr_approved':
        flash('⚠️ Only HR-approved items can be marked paid.', 'warning')
        return redirect(url_for('tr.hr_pending'))

    tr.status = 'paid'
    db.session.add(TRApprovalLog(
        request_id=tr.id, actor_id=current_user.id, actor_role='hr',
        action='paid', remark=(request.form.get('remark') or '').strip()
    ))
    db.session.commit()

    notify_employee(
        tr.employee_id,
        "Reimbursement paid",
        f"Your {_fmt_req_title(tr)} has been marked as paid.",
        url_for('tr.tr_view', req_id=tr.id, _external=True)
    )

    flash('💸 Marked as paid.', 'success')
    return redirect(url_for('tr.hr_pending'))


@tr_bp.route('/hr/disbursement.csv')
@login_required
def hr_disbursement_csv():
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        try:
            return redirect(url_for('employee.dashboard'))
        except BuildError:
            return redirect('/')

    month = request.args.get('month', type=int)
    year = request.args.get('year', type=int)
    if not (month and year):
        flash('Pick month & year to export.', 'warning')
        return redirect(url_for('tr.hr_pending'))

    rows = (TRRequest.query
            .filter(
                TRRequest.month == month,
                TRRequest.year == year,
                TRRequest.status.in_(('hr_approved', 'paid'))
            )
            .order_by(TRRequest.employee_id)
            .all())

    si = StringIO()
    w = csv.writer(si)
    w.writerow(['Employee Code','Employee Name','Bank Name','IFSC','Account No','Type','From Date','To Date','Amount','Month','Year','Status'])

    for r in rows:
        emp = r.employee
        w.writerow([
            getattr(emp, 'employee_code', ''),
            getattr(emp, 'full_name', ''),
            getattr(emp, 'bank_name', '') or '',
            getattr(emp, 'ifsc_code', '') or '',
            getattr(emp, 'bank_account_number', '') or '',
            _rtype_name(r),
            r.from_date,
            r.to_date,
            f'{(r.total_amount or 0):.2f}',
            r.month, r.year, r.status
        ])

    data_bytes = si.getvalue().encode('utf-8-sig')
    bio = BytesIO(data_bytes)
    bio.seek(0)

    filename = f"Reimbursement-Disbursement-{year}-{month:02d}.csv"
    return send_file(
        bio,
        mimetype='text/csv',
        as_attachment=True,
        download_name=filename,
        max_age=0
    )


# ======================================
# “My TR” (employee-scoped)
# ======================================

@tr_bp.route('/my', methods=['GET'])
@login_required
def tr_my():
    month = request.args.get('month', type=int)
    year = request.args.get('year', type=int)
    status = (request.args.get('status') or '').strip().lower()

    q = (TRRequest.query
         .filter(TRRequest.employee_id == current_user.id)
         .order_by(TRRequest.created_on.desc()))

    if month:
        q = q.filter(TRRequest.month == month)
    if year:
        q = q.filter(TRRequest.year == year)
    if status and status != 'all':
        q = q.filter(TRRequest.status == status)

    my_reqs = q.all()
    return render_template('tr/tr_my.html', requests=my_reqs, month=month, year=year, status=status)


# ======================================
# TR Item attachment serve
# ======================================

@tr_bp.route('/item/<int:item_id>/file')
@login_required
def tr_item_file(item_id):
    item = TRItem.query.get_or_404(item_id)

    emp = item.request.employee
    role = getattr(current_user, 'role', None)
    role_name = (getattr(role, 'name', '') or '').lower()
    is_hr_admin = role_name in ('hr', 'hr admin', 'admin', 'system admin', 'superadmin')

    allowed = (
        current_user.id == emp.id or
        current_user.id == (emp.reporting_manager_id or -1) or
        is_hr_admin
    )
    if not allowed:
        abort(403)

    if not item.attachment_path:
        abort(404)

    root = current_app.config.get('UPLOAD_FOLDER') or os.path.join(current_app.root_path, 'uploads')
    rel = item.attachment_path.lstrip('/').replace('\\', '/')
    abs_path = rel if os.path.isabs(rel) else os.path.join(root, rel)

    if not os.path.isfile(abs_path):
        abort(404)

    mime = mimetypes.guess_type(abs_path)[0] or 'application/octet-stream'
    resp = send_file(abs_path, mimetype=mime, as_attachment=False, conditional=True)
    resp.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
    resp.headers['X-Content-Type-Options'] = 'nosniff'
    return resp


# ======================================
# Reimbursement Types Master (HR/Admin)
# ======================================

@tr_bp.route('/types', methods=['GET', 'POST'])
@login_required
def reimbursement_types():
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    if request.method == 'POST':
        name = (request.form.get('name') or '').strip()
        requires_km = bool(request.form.get('requires_km_fields'))
        requires_route = bool(request.form.get('requires_route_fields'))

        if not name:
            flash("Type name is required.", "danger")
            return redirect(url_for('tr.reimbursement_types'))

        existing = ReimbursementType.query.filter(ReimbursementType.name.ilike(name)).first()
        if existing:
            flash("Type already exists.", "warning")
            return redirect(url_for('tr.reimbursement_types'))

        rt = ReimbursementType(
            name=name,
            is_active=True,
            requires_km_fields=requires_km,
            requires_route_fields=requires_route
        )
        db.session.add(rt)
        db.session.commit()
        flash("✅ Type added.", "success")
        return redirect(url_for('tr.reimbursement_types'))

    types = ReimbursementType.query.order_by(ReimbursementType.name.asc()).all()
    return render_template('tr/reimbursement_types.html', types=types)


@tr_bp.route('/types/<int:type_id>/toggle', methods=['POST'])
@login_required
def reimbursement_type_toggle(type_id):
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    rt = ReimbursementType.query.get_or_404(type_id)
    rt.is_active = not rt.is_active
    db.session.commit()
    flash("✅ Updated.", "success")
    return redirect(url_for('tr.reimbursement_types'))


@tr_bp.route('/types/<int:type_id>/edit', methods=['POST'])
@login_required
def reimbursement_type_edit(type_id):
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    rt = ReimbursementType.query.get_or_404(type_id)
    rt.name = (request.form.get('name') or rt.name).strip()
    rt.requires_km_fields = bool(request.form.get('requires_km_fields'))
    rt.requires_route_fields = bool(request.form.get('requires_route_fields'))
    db.session.commit()
    flash("✅ Type updated.", "success")
    return redirect(url_for('tr.reimbursement_types'))

@tr_bp.route('/hr/mark-paid-bulk', methods=['POST'])
@login_required
def hr_mark_paid_bulk():
    if not _is_hr_or_admin(current_user):
        flash('❌ HR/Admin only.', 'danger')
        return redirect(url_for('employee.dashboard'))

    # Accept either "req_ids" (checkboxes) or a comma-separated string
    req_ids = request.form.getlist('req_ids')
    if not req_ids:
        raw = (request.form.get('req_ids_csv') or '').strip()
        req_ids = [x.strip() for x in raw.split(',') if x.strip()]

    try:
        req_ids = [int(x) for x in req_ids]
    except ValueError:
        flash("Invalid selection.", "danger")
        return redirect(url_for('tr.hr_pending'))

    remark = (request.form.get('remark') or '').strip()

    trs = (TRRequest.query
           .filter(TRRequest.id.in_(req_ids))
           .all())

    updated = 0
    skipped = 0

    for tr in trs:
        # Only HR-approved can be marked paid
        if tr.status != 'hr_approved':
            skipped += 1
            continue

        tr.status = 'paid'
        db.session.add(TRApprovalLog(
            request_id=tr.id,
            actor_id=current_user.id,
            actor_role='hr',
            action='paid',
            remark=remark
        ))

        notify_employee(
            tr.employee_id,
            "Reimbursement paid",
            f"Your {_fmt_req_title(tr)} has been marked as paid.",
            url_for('tr.tr_view', req_id=tr.id, _external=True)
        )

        updated += 1

    db.session.commit()

    flash(f"💸 Bulk Paid: {updated} updated, {skipped} skipped (not HR-approved).", "success" if updated else "warning")
    return redirect(url_for('tr.hr_pending'))