from __future__ import annotations

import logging
import time
from dataclasses import dataclass

from django.conf import settings

from jobs.services import emit_progress, renew_lease

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class HandlerContext:
    job_id: str
    worker_id: str
    attempt: int

    def progress(self, percent: int, message: str = "") -> None:
        logger.debug(
            "Handler progress job=%s attempt=%s percent=%s message=%s.",
            self.job_id,
            self.attempt,
            percent,
            message,
        )
        emit_progress(self.job_id, worker_id=self.worker_id, attempt=self.attempt, percent=percent, message=message)

    def renew(self) -> None:
        logger.debug("Handler renewing lease job=%s attempt=%s worker=%s.", self.job_id, self.attempt, self.worker_id)
        renew_lease(
            self.job_id,
            worker_id=self.worker_id,
            attempt=self.attempt,
            lease_seconds=settings.JOB_WORKER_LEASE_SECONDS,
        )


def _sleep_with_progress(context: HandlerContext, seconds: int, *, renew: bool) -> None:
    seconds = max(1, seconds)
    logger.debug(
        "Handler sleep loop started job=%s attempt=%s seconds=%s renew=%s.",
        context.job_id,
        context.attempt,
        seconds,
        renew,
    )
    for elapsed in range(seconds):
        time.sleep(1)
        percent = min(95, int(((elapsed + 1) / seconds) * 100))
        if renew:
            context.renew()
        context.progress(percent, f"{percent}% complete")
    logger.debug("Handler sleep loop finished job=%s attempt=%s.", context.job_id, context.attempt)


def handle_success(payload: dict, context: HandlerContext) -> dict:
    logger.debug("Running success handler job=%s attempt=%s payload=%s.", context.job_id, context.attempt, payload)
    sleep_seconds = int(payload.get("sleep_seconds", 1))
    _sleep_with_progress(context, sleep_seconds, renew=True)
    return {"ok": True, "mode": "success"}


def handle_fail(payload: dict, context: HandlerContext) -> dict:
    logger.debug("Running fail handler job=%s attempt=%s payload=%s.", context.job_id, context.attempt, payload)
    context.progress(25, "Intentional failure started")
    raise RuntimeError(str(payload.get("error", "Intentional demo failure")))


def handle_slow(payload: dict, context: HandlerContext) -> dict:
    logger.debug("Running slow handler job=%s attempt=%s payload=%s.", context.job_id, context.attempt, payload)
    sleep_seconds = int(payload.get("sleep_seconds", 8))
    _sleep_with_progress(context, sleep_seconds, renew=True)
    return {"ok": True, "mode": "slow", "slept_seconds": sleep_seconds}


def handle_timeout(payload: dict, context: HandlerContext) -> dict:
    logger.debug("Running timeout handler job=%s attempt=%s payload=%s.", context.job_id, context.attempt, payload)
    sleep_seconds = int(payload.get("sleep_seconds", settings.JOB_WORKER_LEASE_SECONDS + 10))
    _sleep_with_progress(context, sleep_seconds, renew=False)
    return {"ok": True, "mode": "timeout", "slept_seconds": sleep_seconds}


def handle_flaky(payload: dict, context: HandlerContext) -> dict:
    logger.debug("Running flaky handler job=%s attempt=%s payload=%s.", context.job_id, context.attempt, payload)
    fail_until_attempt = int(payload.get("fail_until_attempt", 2))
    context.progress(35, "Flaky job evaluated")
    if context.attempt <= fail_until_attempt:
        raise RuntimeError(f"Flaky failure on attempt {context.attempt}")
    context.progress(100, "Flaky job succeeded")
    return {"ok": True, "mode": "flaky", "attempt": context.attempt}


HANDLERS = {
    "demo.success": handle_success,
    "demo.fail": handle_fail,
    "demo.slow": handle_slow,
    "demo.timeout": handle_timeout,
    "demo.flaky": handle_flaky,
}


def execute_handler(job, *, worker_id: str) -> dict:
    handler = HANDLERS.get(job.type)
    if handler is None:
        raise ValueError(f"Unknown job type: {job.type}")
    logger.debug("Dispatching job %s type=%s worker=%s attempt=%s.", job.id, job.type, worker_id, job.attempts)
    context = HandlerContext(job_id=str(job.id), worker_id=worker_id, attempt=job.attempts)
    result = handler(job.payload or {}, context)
    logger.debug("Handler finished job %s type=%s result=%s.", job.id, job.type, result)
    return result
