from __future__ import annotations

from django.shortcuts import render
from django.contrib import messages
from django.core.paginator import Paginator
from django.db import transaction
from django.db.models import Count, Max, Subquery, OuterRef, Min, F, ExpressionWrapper, DurationField, BooleanField, Case, When
from django.db.models.functions import Now
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse

from .forms import ScheduleForm, ScriptRootForm
from .models import Script, ScriptRoot, ScriptRun, Schedule
from .services import (
    create_run_record,
    finalize_run,
    run_script_as_runner,
    scan_root,
    set_script_root,
    compute_next_run_at,
)

from django.shortcuts import get_object_or_404


def _get_request_user_hint(request: HttpRequest) -> str | None:
    # Nginx may set REMOTE_USER; if not, keep it blank.
    return request.META.get("REMOTE_USER")


def dashboard(request: HttpRequest) -> HttpResponse:
    root = ScriptRoot.objects.order_by("-updated_at").first()
    scripts_count = Script.objects.count()
    schedules_count = Schedule.objects.count()
    runs_count = ScriptRun.objects.count()
    recent = ScriptRun.objects.select_related("script").order_by("-started_at")[:10]
    return render(
        request,
        "scheduler/dashboard.html",
        {
            "root": root,
            "scripts_count": scripts_count,
            "schedules_count": schedules_count,
            "runs_count": runs_count,
            "recent": recent,
        },
    )


def settings_view(request: HttpRequest) -> HttpResponse:
    current = ScriptRoot.objects.order_by("-updated_at").first()
    if request.method == "POST":
        form = ScriptRootForm(request.POST)
        if form.is_valid():
            root = set_script_root(form.cleaned_data["path"])
            messages.success(request, f"Saved scripts folder: {root.path}")
            return redirect("scheduler:settings")
    else:
        form = ScriptRootForm(initial={"path": current.path if current else "/opt/scripts"})

    return render(request, "scheduler/settings.html", {"form": form, "current": current})


def scan_now(request: HttpRequest) -> HttpResponse:
    if request.method != "POST":
        return redirect("scheduler:settings")

    root = ScriptRoot.objects.order_by("-updated_at").first()
    if not root:
        messages.error(request, "No scripts folder configured.")
        return redirect("scheduler:settings")

    try:
        stats = scan_root(root)
        messages.success(
            request,
            f"Scan complete. Created {stats['created']}, updated {stats['updated']}, marked missing {stats['missing_marked']}.",
        )
    except Exception as e:
        messages.error(request, f"Scan failed: {e}")

    return redirect("scheduler:scripts")


def scripts_list(request: HttpRequest) -> HttpResponse:
    filter_mode = request.GET.get("filter", "visible")

    script_id = request.GET.get("script")

    last_run = ScriptRun.objects.filter(
        script=OuterRef("pk")
    ).order_by("-started_at")

    next_schedule = Schedule.objects.filter(
        script=OuterRef("pk"),
        enabled=True
    ).order_by("next_run_at")

    scripts = (
        Script.objects
        .select_related("root")
        .annotate(run_count=Count("runs", distinct=True))
        .annotate(schedule_count=Count("schedules", distinct=True))
        .annotate(last_run_time=Subquery(last_run.values("started_at")[:1]))
        .annotate(last_status=Subquery(last_run.values("status")[:1]))
        .annotate(last_finished=Subquery(last_run.values("finished_at")[:1]))
        .annotate(
            last_duration=ExpressionWrapper(
                F("last_finished") - F("last_run_time"),
                output_field=DurationField(),
            )
        )
        .annotate(next_run=Subquery(next_schedule.values("next_run_at")[:1]))

        .annotate(
            is_overdue=Case(
                When(next_run__lt=Now(), then=True),
                default=False,
                output_field=BooleanField(),
            )
        )
    )

    # Apply filter AFTER annotation
    if filter_mode == "visible":
        scripts = scripts.filter(hidden=False)
    elif filter_mode == "hidden":
        scripts = scripts.filter(hidden=True)
    elif filter_mode == "all":
        pass

    scripts = scripts.order_by("display_order", "name")

    scripts = list(scripts)

    # fetch recent runs for all scripts
    recent_runs = (
        ScriptRun.objects
        .order_by("script_id", "-started_at")
        .values("script_id", "status")
    )

    runs_by_script = {}

    for r in recent_runs:
        runs_by_script.setdefault(r["script_id"], []).append(r["status"])

    for s in scripts:
        streak = 0
        for status in runs_by_script.get(s.id, []):
            if status == "failed":
                streak += 1
            else:
                break
        s.fail_streak = streak

    return render(
        request,
        "scheduler/scripts_list.html",
        {
            "scripts": scripts,
            "filter_mode": filter_mode,
        },
    )

def script_detail(request: HttpRequest, script_id: int) -> HttpResponse:
    script = get_object_or_404(Script, id=script_id)
    schedules = script.schedules.order_by("-created_at")
    return render(request, "scheduler/script_detail.html", {"script": script, "schedules": schedules})


def run_now(request: HttpRequest, script_id: int) -> HttpResponse:
    if request.method != "POST":
        return redirect("scheduler:script_detail", script_id=script_id)

    script = get_object_or_404(Script, id=script_id)
    if not script.enabled or script.missing:
        messages.error(request, "Script is disabled or missing.")
        return redirect("scheduler:script_detail", script_id=script_id)

    run = create_run_record(script=script, schedule=None, requested_by=_get_request_user_hint(request))
    result = run_script_as_runner(script.abs_path, timeout_seconds=3600)
    finalize_run(run, result)

    if run.status == ScriptRun.STATUS_SUCCESS:
        messages.success(request, "Run completed successfully.")
    else:
        messages.error(request, f"Run finished with status: {run.status} (exit_code={run.exit_code})")

    return redirect("scheduler:run_detail", run_id=run.id)


def runs_list(request: HttpRequest) -> HttpResponse:
    qs = ScriptRun.objects.select_related("script").order_by("-started_at")
    paginator = Paginator(qs, 25)
    page = paginator.get_page(request.GET.get("page", "1"))
    return render(request, "scheduler/runs_list.html", {"page": page})


def run_detail(request: HttpRequest, run_id: int) -> HttpResponse:
    run = get_object_or_404(ScriptRun.objects.select_related("script", "schedule"), id=run_id)
    return render(request, "scheduler/run_detail.html", {"run": run})


def rerun(request: HttpRequest, run_id: int) -> HttpResponse:
    if request.method != "POST":
        return redirect("scheduler:run_detail", run_id=run_id)

    prev = get_object_or_404(ScriptRun.objects.select_related("script"), id=run_id)
    return run_now(request, script_id=prev.script_id)


def schedule_create(request: HttpRequest, script_id: int) -> HttpResponse:
    script = get_object_or_404(Script, id=script_id)
    if request.method == "POST":
        form = ScheduleForm(request.POST)
        if form.is_valid():
            sched = form.save(commit=False)
            sched.script = script
            sched.next_run_at = compute_next_run_at(sched)
            sched.save()
            messages.success(request, "Schedule created.")
            return redirect("scheduler:script_detail", script_id=script.id)
    else:
        form = ScheduleForm(initial={"enabled": True, "kind": Schedule.KIND_INTERVAL, "interval_minutes": 30})

    return render(request, "scheduler/schedule_form.html", {"form": form, "script": script, "mode": "create"})


def schedule_edit(request: HttpRequest, schedule_id: int) -> HttpResponse:
    sched = get_object_or_404(Schedule.objects.select_related("script"), id=schedule_id)
    if request.method == "POST":
        form = ScheduleForm(request.POST, instance=sched)
        if form.is_valid():
            sched = form.save(commit=False)
            # reset next_run when edited
            sched.next_run_at = compute_next_run_at(sched)
            sched.save()
            messages.success(request, "Schedule updated.")
            return redirect("scheduler:script_detail", script_id=sched.script_id)
    else:
        form = ScheduleForm(instance=sched)

    return render(request, "scheduler/schedule_form.html", {"form": form, "script": sched.script, "mode": "edit"})

def toggle_hidden(request, script_id):
    if request.method != "POST":
        return redirect("scheduler:scripts")

    script = get_object_or_404(Script, id=script_id)
    script.hidden = not script.hidden
    script.save(update_fields=["hidden"])

    return redirect("scheduler:scripts")


@transaction.atomic
def clear_scripts(request):
    if request.method != "POST":
        return redirect("scheduler:scripts")

    count = Script.objects.count()
    Script.objects.all().delete()

    messages.success(request, f"Cleared {count} scripts, schedules, and run history.")
    return redirect("scheduler:scripts")

@transaction.atomic
def clear_script_history(request, script_id):
    if request.method != "POST":
        return redirect("scheduler:script_detail", script_id=script_id)

    script = get_object_or_404(Script, id=script_id)

    count = ScriptRun.objects.filter(script=script).count()
    ScriptRun.objects.filter(script=script).delete()

    messages.success(request, f"Cleared {count} history entries for '{script.name}'.")
    return redirect("scheduler:script_detail", script_id=script.id)

def update_display_order(request, script_id):
    if request.method != "POST":
        return redirect("scheduler:scripts")

    script = get_object_or_404(Script, id=script_id)

    try:
        new_order = int(request.POST.get("display_order"))
        script.display_order = new_order
        script.save(update_fields=["display_order"])
    except (TypeError, ValueError):
        pass

    return redirect("scheduler:scripts")

def update_script_settings(request, script_id):
    if request.method != "POST":
        return redirect("scheduler:script_detail", script_id=script_id)

    script = get_object_or_404(Script, id=script_id)

    script.hidden = bool(request.POST.get("hidden"))

    try:
        script.display_order = int(request.POST.get("display_order"))
    except (TypeError, ValueError):
        pass

    script.save(update_fields=["hidden", "display_order"])

    messages.success(request, "Script settings updated.")
    return redirect("scheduler:script_detail", script_id=script.id)

@transaction.atomic
def delete_schedule(request, schedule_id):
    if request.method != "POST":
        return redirect("scheduler:scripts")

    schedule = get_object_or_404(Schedule, id=schedule_id)
    script_id = schedule.script_id
    schedule.delete()

    messages.success(request, "Schedule deleted.")
    return redirect("scheduler:script_detail", script_id=script_id)

