from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone

from apps.accounts.permissions import ROLE_HR_ADMIN, ROLE_SUPER_ADMIN, accessible_company_ids, has_role, require_roles, scope_queryset_by_company
from apps.attendance.models import AttendanceAdjustment
from apps.attendance.forms import AttendanceAdjustmentForm
from apps.attendance.services import create_adjustment_request, create_missing_attendance_adjustment_request, resubmit_attendance_adjustment
from apps.leave.models import LeaveRequest
from apps.leave.services import create_leave_request
from .forms import EmployeeRequestForm, ManagementNoticeForm, TaskDecisionForm
from .models import EmployeeRequest, ManagementNotice, NoticeRecipient, Notification, WorkflowTask
from .services import create_employee_request, decide_task, publish_management_notice, resubmit_employee_request


def _visible_tasks(user):
    qs = WorkflowTask.objects.select_related("assigned_to", "decided_by", "company")
    qs = scope_queryset_by_company(user, qs, "company")
    if user.is_superuser or has_role(user, ROLE_SUPER_ADMIN):
        return qs
    roles = list(user.groups.values_list("name", flat=True))
    return qs.filter(Q(assigned_to=user) | Q(assigned_role__in=roles))


def _task_source_summary(task):
    source_type = (task.source_type or "").lower().strip()
    source_id = task.source_id
    if not source_type or not source_id:
        return []
    if source_type == "employee_request":
        item = EmployeeRequest.objects.select_related("employee").filter(pk=source_id, employee__company_id=task.company_id).first()
        if not item:
            return []
        rows = [
            ("Employee", str(item.employee)),
            ("Request Type", item.request_type.replace("_", " ").title()),
            ("Subject", item.subject or "—"),
            ("Details", item.details or "—"),
        ]
        for key, value in (item.payload or {}).items():
            rows.append((key.replace("_", " ").title(), value))
        return rows
    if source_type == "leave_request":
        item = LeaveRequest.objects.select_related("employee", "leave_type").filter(pk=source_id, employee__company_id=task.company_id).first()
        if not item:
            return []
        return [
            ("Employee", str(item.employee)),
            ("Leave Type", item.leave_type.name),
            ("Dates", f"{item.start_date} to {item.end_date}"),
            ("Days", item.days),
            ("Reason", item.reason or "—"),
        ]
    if source_type == "attendance_adjustment":
        item = AttendanceAdjustment.objects.select_related("attendance__employee").filter(pk=source_id, attendance__employee__company_id=task.company_id).first()
        if not item:
            return []
        return [
            ("Employee", str(item.attendance.employee)),
            ("Attendance Date", item.attendance.work_date),
            ("Current Values", item.old_values),
            ("Requested Values", item.new_values),
            ("Reason", item.reason),
        ]
    if source_type == "overtime_entry":
        from apps.attendance.models import OvertimeEntry
        item = OvertimeEntry.objects.select_related("employee").filter(
            pk=source_id, employee__company_id=task.company_id
        ).first()
        if not item:
            return []
        return [
            ("Employee", str(item.employee)), ("Work Date", item.work_date),
            ("Requested Minutes", item.requested_minutes), ("Rate Multiplier", item.rate_multiplier),
            ("Status", item.status), ("Reason", item.reason or "—"),
        ]
    if source_type == "bonus":
        from apps.payroll.models import Bonus
        item = Bonus.objects.select_related("employee").filter(
            pk=source_id, employee__company_id=task.company_id
        ).first()
        if not item:
            return []
        return [
            ("Employee", str(item.employee)), ("Payroll Month", item.payroll_month),
            ("Bonus Type", item.bonus_type), ("Amount", item.amount),
            ("Status", item.status), ("Reason", item.reason or "—"),
        ]
    if source_type == "deduction":
        from apps.payroll.models import Deduction
        item = Deduction.objects.select_related("employee").filter(
            pk=source_id, employee__company_id=task.company_id
        ).first()
        if not item:
            return []
        return [
            ("Employee", str(item.employee)), ("Payroll Month", item.payroll_month),
            ("Deduction Type", item.deduction_type), ("Amount", item.amount),
            ("Status", item.status), ("Reason", item.reason or "—"),
        ]
    if source_type == "clearance_item":
        from apps.exits.models import ClearanceItem
        item = ClearanceItem.objects.select_related("exit_case__employee").filter(pk=source_id, exit_case__employee__company_id=task.company_id).first()
        if not item:
            return []
        return [("Employee", str(item.exit_case.employee)), ("Clearance", item.clearance_type.replace("_", " ").title()), ("Status", item.status), ("Note", item.note or "—")]
    if source_type == "final_settlement":
        from apps.exits.models import FinalSettlement
        item = FinalSettlement.objects.select_related("exit_case__employee").filter(pk=source_id, exit_case__employee__company_id=task.company_id).first()
        if not item:
            return []
        return [("Employee", str(item.exit_case.employee)), ("Payroll Due", item.payroll_due_amount), ("Leave Encashment", item.leave_encashment), ("Outstanding Loan Recovery", item.loan_outstanding_recovery), ("Other Deductions", item.other_deductions), ("Net Payable", item.net_payable)]
    if source_type == "loan_disbursement":
        from apps.loans.models import LoanAccount
        item = LoanAccount.objects.select_related("employee").filter(pk=source_id, employee__company_id=task.company_id).first()
        if not item:
            return []
        return [
            ("Employee", str(item.employee)),
            ("Facility", f"{item.get_loan_type_display()} #{item.pk}"),
            ("Request Reference", item.request_reference or "—"),
            ("Sanctioned Amount", item.sanctioned_amount),
            ("Installments", item.installment_count),
            ("Recovery Starts", item.start_month),
            ("Status", item.get_status_display()),
        ]
    return [("Source Type", task.source_type), ("Source ID", task.source_id)]


@login_required
def application_list(request):
    employee = getattr(request.user, "employee_profile", None)
    company_ids = accessible_company_ids(request.user)
    items = []
    if employee and employee.company_id in company_ids:
        for r in EmployeeRequest.objects.filter(employee=employee):
            items.append({"reference": r.reference_no, "type": r.request_type.replace("_", " ").title(), "subject": r.subject or r.details[:80], "status": r.get_status_display(), "response": r.rejection_reason or r.resolution_note, "created_at": r.created_at, "resubmit_url": reverse("workflow:application-resubmit", args=[r.pk]) if r.status == EmployeeRequest.Status.RETURNED else ""})
        for r in LeaveRequest.objects.select_related("leave_type").filter(employee=employee):
            items.append({"reference": f"LEAVE-{r.pk:06d}", "type": "Leave Application", "subject": f"{r.leave_type.name}: {r.start_date} to {r.end_date}", "status": r.status.replace("_", " ").title(), "response": r.rejection_reason or r.reviewer_note, "created_at": r.created_at, "resubmit_url": reverse("leave:resubmit", args=[r.pk]) if r.status == "RETURNED" else ""})
        correction_qs = AttendanceAdjustment.objects.select_related("attendance", "requested_by_user").filter(
            Q(attendance__employee=employee) | Q(requested_by_user=request.user)
        ).distinct()
        for a in correction_qs:
            can_resubmit = a.status == "RETURNED" and (a.requested_by_user_id in {None, request.user.id})
            items.append({"reference": f"ATT-ADJ-{a.pk:06d}", "type": "Attendance Correction", "subject": f"Attendance: {a.attendance.work_date}", "status": a.status.replace("_", " ").title(), "response": a.reason if a.status in {"REJECTED", "RETURNED"} else "", "created_at": a.created_at, "resubmit_url": reverse("workflow:attendance-resubmit", args=[a.pk]) if can_resubmit else ""})
    items.sort(key=lambda x: x["created_at"], reverse=True)
    notices = []
    if employee and employee.company_id in company_ids:
        now = timezone.now()
        notices = list(NoticeRecipient.objects.select_related("notice").filter(
            employee=employee, notice__publish_from__lte=now
        ).filter(Q(notice__publish_until__isnull=True) | Q(notice__publish_until__gte=now)).order_by("-notice__publish_from")[:100])
    can_publish_notice = request.user.is_superuser or has_role(request.user, ROLE_SUPER_ADMIN, ROLE_HR_ADMIN)
    return render(request, "workflow/application_list.html", {"items": items, "notices": notices, "can_publish_notice": can_publish_notice})


@login_required
def application_create(request):
    employee = getattr(request.user, "employee_profile", None)
    company_ids = accessible_company_ids(request.user)
    if not employee or employee.company_id not in company_ids:
        messages.error(request, "Employee applications must be submitted from a user account linked to an employee profile. Super/HR administrators can review and manage requests, but cannot submit a personal employee request without a linked employee profile.")
        return redirect("workflow:applications")
    initial = {}
    requested_type = request.GET.get("type", "").upper()
    valid_types = dict(EmployeeRequestForm.base_fields["request_type"].choices)
    if requested_type in valid_types:
        initial["request_type"] = requested_type
    form = EmployeeRequestForm(request.POST or None, request.FILES or None, employee=employee, initial=initial)
    if request.method == "POST" and form.is_valid():
        request_type = form.cleaned_data["request_type"]
        if request_type == "LEAVE":
            create_leave_request(
                employee=employee,
                leave_type=form.cleaned_data["leave_type"],
                start_date=form.cleaned_data["leave_start_date"],
                end_date=form.cleaned_data["leave_end_date"],
                days=form.cleaned_data["calculated_leave_days"],
                reason=form.cleaned_data.get("details", ""),
                user=request.user,
                request=request,
            )
        elif request_type == "ATTENDANCE_CORRECTION":
            record = form.cleaned_data.get("attendance_record")
            correction_data = {
                # Blank values are intentional: they allow an incorrect punch to be cleared.
                "new_check_in": form.cleaned_data.get("corrected_check_in"),
                "new_check_out": form.cleaned_data.get("corrected_check_out"),
                "new_status": form.cleaned_data.get("corrected_status"),
                "reason": form.cleaned_data["details"],
            }
            if record is None:
                create_missing_attendance_adjustment_request(
                    employee=employee, work_date=form.cleaned_data["attendance_date"],
                    cleaned_data=correction_data, user=request.user, request=request,
                )
            else:
                create_adjustment_request(
                    attendance=record, cleaned_data=correction_data,
                    user=request.user, request=request,
                )
        else:
            create_employee_request(employee=employee, cleaned_data=form.cleaned_data, user=request.user, request=request)
        messages.success(request, "Application submitted successfully.")
        return redirect("workflow:applications")
    return render(request, "workflow/application_form.html", {"form": form})


@login_required
def task_list(request):
    status = request.GET.get("status", "OPEN").strip().upper()
    tasks = _visible_tasks(request.user)
    if status in {"OPEN", "APPROVED", "REJECTED", "RETURNED", "CANCELLED"}:
        tasks = tasks.filter(status=status)
    return render(request, "workflow/task_list.html", {"tasks": tasks.order_by("-created_at")[:300], "status_filter": status})


@login_required
def task_review(request, pk):
    task = get_object_or_404(_visible_tasks(request.user), pk=pk)
    form = TaskDecisionForm(request.POST or None, task=task)
    if request.method == "POST" and form.is_valid():
        try:
            decide_task(task=task, user=request.user, decision=form.cleaned_data["decision"], note=form.cleaned_data.get("note", ""), decision_data=form.cleaned_data)
            messages.success(request, "Decision saved and the source record was updated.")
            return redirect("workflow:tasks")
        except ValidationError as exc:
            form.add_error(None, exc)
    special_mode = ""
    source_type = (task.source_type or "").lower()
    if source_type == "employee_request":
        item = EmployeeRequest.objects.filter(pk=task.source_id, employee__company_id=task.company_id).first()
        if item and item.request_type in {"LOAN", "SALARY_ADVANCE"}:
            special_mode = "loan_sanction"
    elif source_type == "loan_disbursement":
        special_mode = "loan_disbursement"
    return render(request, "workflow/task_review.html", {"task": task, "form": form, "source_summary": _task_source_summary(task), "special_mode": special_mode})


@login_required
def notification_list(request):
    notifications = Notification.objects.filter(recipient=request.user).order_by("-created_at")[:300]
    if request.method == "POST":
        Notification.objects.filter(recipient=request.user, read_at__isnull=True).update(read_at=timezone.now())
        return redirect("workflow:notifications")
    return render(request, "workflow/notification_list.html", {"notifications": notifications})


@require_roles(ROLE_SUPER_ADMIN, ROLE_HR_ADMIN)
def notice_create(request):
    from apps.organization.models import Company
    company_qs = Company.objects.filter(pk__in=accessible_company_ids(request.user), is_active=True)
    form = ManagementNoticeForm(request.POST or None, company_queryset=company_qs, initial={"publish_from": timezone.localtime().strftime("%Y-%m-%dT%H:%M")})
    if request.method == "POST" and form.is_valid():
        publish_management_notice(cleaned_data=form.cleaned_data, user=request.user, request=request)
        messages.success(request, "Management notice published and employee notifications created.")
        return redirect("workflow:applications")
    return render(request, "workflow/notice_form.html", {"form": form})


@login_required
def notice_mark_read(request, pk):
    if request.method != "POST":
        return redirect("workflow:applications")
    employee = getattr(request.user, "employee_profile", None)
    if not employee:
        raise PermissionDenied("Employee profile required.")
    recipient = get_object_or_404(NoticeRecipient.objects.select_related("notice"), pk=pk, employee=employee, notice__company_id__in=accessible_company_ids(request.user))
    if not recipient.read_at:
        recipient.read_at = timezone.now()
        recipient.save(update_fields=["read_at", "updated_at"])
    return redirect("workflow:applications")


@login_required
def application_resubmit(request, pk):
    employee = getattr(request.user, "employee_profile", None)
    item = get_object_or_404(EmployeeRequest.objects.select_related("employee"), pk=pk, employee=employee)
    if item.status != EmployeeRequest.Status.RETURNED:
        messages.error(request, "Only a returned application can be resubmitted.")
        return redirect("workflow:applications")
    payload = item.payload or {}
    initial = {"request_type": item.request_type, "subject": item.subject, "details": item.details}
    initial.update(payload)
    form = EmployeeRequestForm(request.POST or None, request.FILES or None, employee=employee, initial=initial)
    form.fields["request_type"].disabled = True
    if request.method == "POST" and form.is_valid():
        try:
            resubmit_employee_request(request_obj=item, cleaned_data=form.cleaned_data, user=request.user, request=request)
            messages.success(request, "Corrected application resubmitted.")
            return redirect("workflow:applications")
        except ValidationError as exc:
            form.add_error(None, exc)
    return render(request, "workflow/application_form.html", {"form": form, "resubmitting": True})


@login_required
def attendance_resubmit(request, pk):
    employee = getattr(request.user, "employee_profile", None)
    adjustment = get_object_or_404(
        AttendanceAdjustment.objects.select_related("attendance__employee", "requested_by_user"),
        Q(attendance__employee=employee) | Q(requested_by_user=request.user),
        pk=pk,
    )
    if adjustment.requested_by_user_id and adjustment.requested_by_user_id != request.user.id:
        raise PermissionDenied("Only the original requester may resubmit this attendance correction.")
    if adjustment.status != "RETURNED":
        messages.error(request, "Only a returned attendance correction can be resubmitted.")
        return redirect("workflow:applications")
    values = adjustment.new_values or {}
    initial = {
        "new_check_in": values.get("check_in"), "new_check_out": values.get("check_out"),
        "new_status": values.get("status") or adjustment.attendance.status, "reason": adjustment.reason,
    }
    form = AttendanceAdjustmentForm(request.POST or None, initial=initial)
    if request.method == "POST" and form.is_valid():
        try:
            resubmit_attendance_adjustment(adjustment, cleaned_data=form.cleaned_data, user=request.user, request=request)
            messages.success(request, "Corrected attendance request resubmitted.")
            return redirect("workflow:applications")
        except ValidationError as exc:
            form.add_error(None, exc)
    return render(request, "workflow/attendance_resubmit_form.html", {"form": form, "adjustment": adjustment})
