"""huey instance for huey + Flask REST API (to be called by huey_consumer.py)."""

import json
import os
import time
import typing

import huey.signals as sigs
from huey import SqliteHuey
from huey.api import Result

from ripple1d.api.log import initialize_log
from ripple1d.api.utils import tracerbacker

LOG = initialize_log()

huey = SqliteHuey(filename=os.path.join("huey.db"), results=True)

# Create custom table task_status
huey.storage.sql(
    """
    create table if not exists "task_status" (
        "task_id" text not null,
        "func_name" text not null,
        "func_kwargs" text not null,
        "huey_status" text not null,
        "ogc_status" text not null,
        "accept_time" timestamptz not null,
        "dismiss_time" timestamptz,
        "start_time" timestamptz,
        "finish_time" timestamptz,
        "status_time" timestamptz,
        "finish_duration_minutes" real,
        primary key("task_id")
    );
    """,
    commit=True,
)

# Create custom triggers to keep status_time field updated
huey.storage.sql(
    """
    create trigger if not exists "trg_task_status_insert" after insert on "task_status"
    begin
        update "task_status" set
            "status_time" = max(
            ifnull("accept_time", datetime(0)),
            ifnull("dismiss_time", datetime(0)),
            ifnull("start_time", datetime(0)),
            ifnull("finish_time", datetime(0))
        ),
            "finish_duration_minutes" = 1440.0 * (julianday("finish_time") - julianday("start_time"))
        where "task_id" = NEW."task_id";
    end;
    """,
    commit=True,
)
huey.storage.sql(
    """
    create trigger if not exists "trg_task_status_update" after update on "task_status"
    begin
        update "task_status" set
            "status_time" = max(
            ifnull("accept_time", datetime(0)),
            ifnull("dismiss_time", datetime(0)),
            ifnull("start_time", datetime(0)),
            ifnull("finish_time", datetime(0))
        ),
            "finish_duration_minutes" = 1440.0 * (julianday("finish_time") - julianday("start_time"))
        where "task_id" = NEW."task_id";
    end;
    """,
    commit=True,
)


def create_and_enqueue_task(func: typing.Callable, kwargs: dict = {}) -> Result:
    """Create a task instance, then add it to the custom table "task_status" before adding it to the queue.

    Return the Result object associated with the task, which is the same class type that would be returned by
    calling the @huey.task()-decorated function itself (in this case, `_process`).

    This is needed to avoid a race condition, this ensures that the task exists in the custom table before
    it starts executing.
    """
    task_instance = _process.s(func, kwargs)
    huey.storage.sql(
        """
    insert into "task_status"
        ("task_id", "func_name", "func_kwargs", "huey_status", "ogc_status", "accept_time")
    values
        (?, ?, ?, 'queued', 'accepted', datetime('now'))""",
        (task_instance.id, func.__name__, json.dumps(kwargs)),
        True,
    )
    return huey.enqueue(task_instance)


def revoke_task(task_id: str):
    """Revoke a task."""
    huey.revoke_by_id(task_id)
    expression = f"""
    update "task_status"
    set
        "ogc_status" = 'dismissed',
        "dismiss_time" = datetime('now')
    where "task_id" = ?
    """
    args = (task_id,)
    huey.storage.sql(expression, args, True)


def task_status(only_task_id: str | None) -> dict[str, dict]:
    """Return dictionary of tasks, where key is task ID and value is subdict (fields related to each task).

    If only_task_id is None, all tasks will be returned.
    If only_task_id is *not* None, only the provided task will be returned.
    """
    expression = """
        select
            "task_id",
            "func_name",
            "func_kwargs",
            "huey_status",
            "ogc_status",
            "accept_time",
            "dismiss_time",
            "start_time",
            "finish_time",
            "status_time",
            "finish_duration_minutes"
        from "task_status"
        order by "status_time" desc
        """
    results_raw = huey.storage.sql(expression, results=True)
    results_dict = {}
    for (
        task_id,
        func_name,
        func_kwargs,
        huey_status,
        ogc_status,
        accept_time,
        dismiss_time,
        start_time,
        finish_time,
        status_time,
        finish_duration_minutes,
    ) in results_raw:
        if only_task_id is not None and task_id != only_task_id:
            continue

        results_dict[task_id] = {
            "huey_status": huey_status,
            "func_name": func_name,
            "func_kwargs": func_kwargs,
            "ogc_status": ogc_status,
            "accept_time": accept_time,
            "dismiss_time": dismiss_time,
            "start_time": start_time,
            "finish_time": finish_time,
            "status_time": status_time,
            "finish_duration_minutes": finish_duration_minutes,
        }
    return results_dict


def huey_status(task_id: str) -> str:
    """For given task ID, return its current status in huey terms."""
    expression = """select "huey_status" from "task_status" where "task_id" = ?"""
    results = huey.storage.sql(expression, (task_id,), results=True)
    if not results:
        return "notfound"
    return results[0][0]


def ogc_status(task_id: str) -> str:
    """For given task ID, return its current status in OGC API terms.

    WARNING assumes function executed by task is wrapped by `tracerbacker`
    """
    expression = """select "ogc_status" from "task_status" where "task_id" = ?"""
    results = huey.storage.sql(expression, (task_id,), results=True)
    if not results:
        return "notfound"
    return results[0][0]


def noop():
    """Do nothing except log a message. For ping endpoint and testing."""
    LOG.info("This message is from the noop function")
    pass


def sleep15():
    """Do nothing except log a message and then sleep for a while.  For testing."""
    LOG.info("This message is from the sleep15 function")
    time.sleep(15)
    return ("Slept for 15", 123.456)


@huey.task(context=True)
@tracerbacker
def _process(func: typing.Callable, kwargs: dict = {}, task=None):
    """Execute generic huey task that calls the provided func with provided kwargs, asynchronously.

    task expected for all funcitons in the ops module to pass through task id for logging
    """
    if task:
        kwargs["task_id"] = task.id
    return func(**kwargs)


@huey.signal()
def _handle_signals(signal, task, exc=None):
    """Update the status in the task_status table When task emits a signal."""
    match signal:
        case sigs.SIGNAL_EXECUTING:
            time_field = "start_time"
            ogc_status = "running"

        case sigs.SIGNAL_COMPLETE | sigs.SIGNAL_ERROR:
            time_field = "finish_time"
            tracerbacker_return = huey.result(task.id, preserve=True)
            if tracerbacker_return is None:
                ogc_status = "notfound"
            else:
                if tracerbacker_return["err"] is None:
                    ogc_status = "successful"
                else:
                    ogc_status = "failed"

        case sigs.SIGNAL_CANCELED | sigs.SIGNAL_LOCKED | sigs.SIGNAL_EXPIRED | sigs.SIGNAL_INTERRUPTED:
            time_field = "dismiss_time"
            ogc_status = "dismissed"

        case sigs.SIGNAL_REVOKED:
            # Set huey status, then short-circuit without setting "ogc_status" or time field.
            # OGC status and dismiss time field are handled at time of revoke call, rather than waiting
            # for the revoke signal which happens later.
            huey.storage.sql(
                """update "task_status" set huey_status = ? where "task_id" = ?""", (signal, task.id), True
            )
            return

        case _:  # e.g. SIGNAL_RETRYING, SIGNAL_SCHEDULED
            raise ValueError(f"Unhandled signal: {signal}")

    expression = f"""
        update "task_status"
        set
            "huey_status" = ?,
            "ogc_status" = ?,
            "{time_field}" = datetime('now')
        where "task_id" = ?
        """

    args = (signal, ogc_status, task.id)
    huey.storage.sql(expression, args, True)
