"""
IMPORTANT
----------

These tests are intended to prevent us from changing defaults in one place and
forgetting to change them in other places, or otherwise breaking the default handling logic.
If you get a failure here, you likely changed the default values for some of the params (below)
in one of the task decorators e.g. `Workflow.task`, etc.

The intention of these tests is to:
 1. Ensure that the behavior of falling back to `TaskDefaults` works as expected, which means that
    if no value for a certain parameter to one of these decorators is provided, it should fall back to the
    value in `TaskDefaults` if one is set.
2. Ensure that the default values set in the rest of the codebase don't change, and are consistent with each other.

If you change the default values for any of these parameters, please update the tests accordingly.
"""

from datetime import timedelta
from typing import Any

import pytest

from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet, Task, TaskDefaults


def dummy_task(input: EmptyModel, context: Context) -> dict[str, str]:
    return {"foo": "bar"}


async def dummy_durable_task(
    input: EmptyModel, context: DurableContext
) -> dict[str, str]:
    return {"foo": "bar"}


DEFAULT_SCHEDULE_TIMEOUT = timedelta(minutes=5)
DEFAULT_EXECUTION_TIMEOUT = timedelta(seconds=60)
DEFAULT_RETRIES = 0
DEFAULT_BACKOFF_FACTOR = None
DEFAULT_BACKOFF_MAX_SECONDS = None


def task(
    hatchet: Hatchet,
    is_durable: bool,
    task_defaults: TaskDefaults,
    **kwargs: Any,
) -> Task[EmptyModel, dict[str, str]]:
    workflow = hatchet.workflow(
        name="foo",
        task_defaults=task_defaults,
    )

    task_fn = workflow.durable_task if is_durable else workflow.task

    return task_fn(**kwargs)(dummy_durable_task if is_durable else dummy_task)  # type: ignore


def standalone_task(
    hatchet: Hatchet,
    is_durable: bool,
    **kwargs: Any,
) -> Task[EmptyModel, dict[str, str]]:
    task_fn = hatchet.durable_task if is_durable else hatchet.task

    return task_fn(**kwargs)(dummy_durable_task if is_durable else task)._task  # type: ignore


@pytest.mark.parametrize("is_durable", [False, True])
def test_task_defaults_applied_correctly(hatchet: Hatchet, is_durable: bool) -> None:
    schedule_timeout = timedelta(seconds=3)
    execution_timeout = timedelta(seconds=1)
    retries = 4
    backoff_factor = 1
    backoff_max_seconds = 5

    t = task(
        hatchet=hatchet,
        is_durable=is_durable,
        task_defaults=TaskDefaults(
            schedule_timeout=schedule_timeout,
            execution_timeout=execution_timeout,
            retries=retries,
            backoff_factor=backoff_factor,
            backoff_max_seconds=backoff_max_seconds,
        ),
    )

    assert t.schedule_timeout == schedule_timeout
    assert t.execution_timeout == execution_timeout
    assert t.retries == retries
    assert t.backoff_factor == backoff_factor
    assert t.backoff_max_seconds == backoff_max_seconds


@pytest.mark.parametrize(
    "is_durable,is_standalone",
    [
        (False, False),
        (True, False),
        (False, True),
        (True, True),
    ],
)
def test_fallbacking_ensure_default_unchanged(
    hatchet: Hatchet, is_durable: bool, is_standalone: bool
) -> None:
    t = task(
        hatchet=hatchet,
        is_durable=is_durable,
        task_defaults=TaskDefaults(),
    )

    """If this test fails, it means that you changed the default values for the params to one of the `task` or `durable_task` decorators"""
    assert t.schedule_timeout == DEFAULT_SCHEDULE_TIMEOUT
    assert t.execution_timeout == DEFAULT_EXECUTION_TIMEOUT
    assert t.retries == DEFAULT_RETRIES
    assert t.backoff_factor == DEFAULT_BACKOFF_FACTOR
    assert t.backoff_max_seconds == DEFAULT_BACKOFF_MAX_SECONDS


@pytest.mark.parametrize(
    "is_durable,is_standalone",
    [
        (False, False),
        (True, False),
        (False, True),
        (True, True),
    ],
)
def test_defaults_correctly_overridden_by_params_passed_in(
    hatchet: Hatchet, is_durable: bool, is_standalone: bool
) -> None:
    t = task(
        hatchet=hatchet,
        is_durable=is_durable,
        task_defaults=TaskDefaults(
            schedule_timeout=timedelta(seconds=3),
            execution_timeout=timedelta(seconds=1),
            retries=4,
            backoff_factor=1,
            backoff_max_seconds=5,
        ),
        schedule_timeout=timedelta(seconds=9),
        execution_timeout=timedelta(seconds=2),
        retries=6,
        backoff_factor=3,
        backoff_max_seconds=5,
    )

    assert t.schedule_timeout == timedelta(seconds=9)
    assert t.execution_timeout == timedelta(seconds=2)
    assert t.retries == 6
    assert t.backoff_factor == 3
    assert t.backoff_max_seconds == 5


@pytest.mark.parametrize(
    "is_durable,is_standalone",
    [
        (False, False),
        (True, False),
        (False, True),
        (True, True),
    ],
)
def test_params_correctly_set_with_no_defaults(
    hatchet: Hatchet, is_durable: bool, is_standalone: bool
) -> None:
    t = task(
        hatchet=hatchet,
        is_durable=is_durable,
        task_defaults=TaskDefaults(),
        schedule_timeout=timedelta(seconds=9),
        execution_timeout=timedelta(seconds=2),
        retries=6,
        backoff_factor=3,
        backoff_max_seconds=5,
    )

    assert t.schedule_timeout == timedelta(seconds=9)
    assert t.execution_timeout == timedelta(seconds=2)
    assert t.retries == 6
    assert t.backoff_factor == 3
    assert t.backoff_max_seconds == 5
