"""Test sorting of classes."""

from __future__ import annotations

import random
from typing import TYPE_CHECKING

import pytest

from tailwhip.process import process_css, process_html, process_text
from tailwhip.sorting import sort_classes

if TYPE_CHECKING:
    from tailwhip.datatypes import Config


def shuffle(lst: list[str]) -> list[str]:
    """Shuffle a list of strings."""
    return random.sample(lst, len(lst))


CLASS_GROUPS = (
    # Container comes always first.
    [
        "container",
        "m-2",
        "font-bold",
        "text-xl",
    ],
    #  Margin first, then padding. Then in clockwise order.
    #
    #  m ->
    #      none -> x -> y -> t -> r -> b -> l
    #  p->
    #      none -> x -> y -> t -> r -> b -> l
    [
        "m-2",
        "mx-64",
        "my-128",
        "mt-4",
        "mr-8",
        "mb-16",
        "ml-32",
        "p-1",
        "px-32",
        "py-64",
        "pt-2",
        "pr-4",
        "pb-8",
        "pl-16",
    ],
    # Negative values don't change order.
    [
        "m-2",
        "-mx-64",
        "-my-128",
        "mt-4",
        "-mr-8",
        "mb-16",
        "-ml-32",
        "p-1",
        "-px-32",
        "py-64",
        "-pt-2",
        "-pr-4",
        "pb-8",
        "pl-16",
    ],
    # Size, W, Min-W, Max-W, H, Min-H, Max-H, Aspect.
    [
        "size-4",
        "w-10",
        "min-w-full",
        "max-w-md",
        "h-12",
        "min-h-screen",
        "max-h-96",
        "aspect-square",
    ],
    # Same, but more complex values
    [
        "size-[40px]",
        "w-[72rem]",
        "min-w-[320px]",
        "max-w-5xl",
        "h-[calc(100vh-4rem)]",
        "min-h-[60vh]",
        "max-h-[800px]",
        "aspect-[3/2]",
    ],
    # min-*: then max-*:
    [
        "text-base",
        "min-[320px]:text-sm",
        "max-[1024px]:hidden",
        "max-[320px]:text-sm",
    ],
    # Font first, then Text, Colors Last
    [
        r"font-light",
        r"font-gray-500",
        r"text-pretty",
        r"text-xl",
        r"text-black/90",
        r"text-red-400",
    ],
    # Breakpoints: none -> sm -> md -> lg -> xl -> 2xl
    [
        "p-2",
        "font-light",
        "text-sm",
        "text-gray-600",
        "sm:p-4",
        "sm:font-normal",
        "sm:text-base",
        "sm:text-gray-700",  # Color is last
        "md:p-6",
        "md:font-medium",
        "md:text-lg",
        "md:text-gray-800",
        "lg:p-8",
        "lg:font-semibold",
        "lg:text-xl",
        "lg:text-gray-900",
        "xl:p-10",
        "xl:font-bold",
        "xl:text-2xl",
        "xl:text-black",
        "2xl:p-12",
        "2xl:font-extrabold",
        "2xl:text-3xl",
        "2xl:text-black/90",
    ],
    # first: -> last: -> odd: -> even:
    [
        "first:border-t-0",
        "last:border-b-0",
        "odd:bg-gray-100",
        "even:bg-white",
    ],
    # dark: -> sm: -> md: -> lg: -> etc. And also within sub groups.
    [
        "dark:text-white",
        "dark:md:hover:text-blue-300",
        "dark:lg:hover:bg-gray-900",
        "sm:disabled:opacity-50",
        "md:first:px-4",
        "md:focus:text-white",
        "lg:text-xl",
        "lg:focus:hover:bg-blue-500",
        "first:mt-0",
        "focus:text-black",
        "hover:bg-red-500",
        "group-hover:bg-blue-500",
        "peer-checked:bg-green-500",
    ],
    [
        "border-1",  # No group, No color
        "border-t-2",
        "border-red-400",  # No group, Color
        "border-t-blue-500",
        "dark:border-1",  # Dark first
        "dark:border-t-2",
        "dark:border-red-400",  # Dark colors
        "dark:border-t-blue-500",
        "sm:border-1",  # Breakpoint before other
        "sm:border-t-2",
        "sm:border-red-400",
        "sm:border-t-blue-500",
        "focus:sm:border-1",  # Small breakpoint before large
        "focus:sm:border-t-2",
        "focus:sm:border-red-400",
        "focus:sm:border-t-blue-500",
        "focus:lg:border-1",
        "focus:lg:border-t-2",
        "focus:lg:border-red-400",
        "focus:lg:border-t-blue-500",
        "focus:border-1",  # No breakpoint after other
        "focus:border-t-2",
        "focus:border-red-400",
        "focus:border-t-blue-500",
    ],
    # Grid -> Cols -> Rows -> Gap
    [
        "grid",
        "grid-cols-[200px_1fr_2fr]",
        "grid-rows-4",
        "gap-4",
    ],
    # Gradient From -> To -> Via
    [
        "m-4",
        "bg-gradient-to-br",
        "from-pink-500",
        "to-yellow-500",
        "via-red-500",
    ],
    # Backdrop Blue Brightness etc.
    [
        "p-6",
        "bg-white/50",
        "backdrop-blur-md",
        "backdrop-brightness-75",
        "backdrop-contrast-125",
        "hue-rotate",
    ],
    # Arbitrary HTML
    [
        "p-2",
        "[&_a]:hover:underline",
        "[&>*]:p-4",
        "[&_a]:text-blue-500",
    ],
    # After before Before
    [
        "after:content-['>']",
        "before:content-['']",
        "before:content-['★']",
    ],
    # Container queries come before variants
    [
        "p-4",
        "@container:p-4",
        "@lg:text-xl",
        "@xl:grid-cols-3",
        "dark:p-4",
        "lg:p-4",
        "xl:p-4",
    ],
)


@pytest.mark.parametrize("classes", CLASS_GROUPS)
@pytest.mark.parametrize("iteration", range(10))
def test_sorting(classes: list[str], config: Config, iteration: int) -> None:  # noqa: ARG001
    """Test sorting of classes.

    The classes in the parameter are the correct order. We shuffle them before
    we do the comparision. To mitigate randomness, we run the test multiple times.
    """
    result = sort_classes(shuffle(classes), config)
    assert result == classes


@pytest.mark.parametrize("iteration", range(10))
def test_sorting_custom_colors(config: Config, iteration: int) -> None:  # noqa: ARG001
    """Custom colors are sorted along other colors."""
    config.custom_colors = {"primary", "secondary-500", "border-almond-500"}
    classes = [
        "border-1",  # Non colors
        "border-b-4",
        "border-t-2",
        "border-almond-500",  # Border Colors
        "border-primary",
        "border-red-400",
        "border-t-blue-500",  # Border Top Colors
        "border-t-secondary-500",
    ]

    result = sort_classes(shuffle(classes), config)
    assert result == classes


def test_kitchen_sink_example(config: Config) -> None:
    """Test a complex real-world example with many advanced features."""
    html = """
<div class="group relative p-[calc(1rem+2px)] m-[clamp(1rem,5vw,3rem)]
            backdrop-blur-md backdrop-brightness-75 bg-white/80
            before:content-[''] before:absolute before:inset-0
            hover:backdrop-blur-lg lg:hover:backdrop-brightness-90
            [&>*]:p-4 [&_a]:text-blue-500 [&_a]:hover:underline
            grid grid-cols-[200px_1fr_2fr] gap-[calc(1rem+2px)]
            min-[320px]:grid-cols-1 min-[768px]:grid-cols-2
            shadow-[0_35px_35px_rgba(0,0,0,0.25)]">
    <h1 class="backdrop-blur-md before:content-[''] [&>*]:p-4
               grid-cols-[200px_1fr] p-[calc(1rem+2px)]
               group-hover:bg-blue-500 shadow-[0_10px_20px_rgba(0,0,0,0.1)]">Test</h1>
</div>
    """
    expected = """
<div class="grid grid-cols-[200px_1fr_2fr] relative gap-[calc(1rem+2px)] m-[clamp(1rem,5vw,3rem)] p-[calc(1rem+2px)] bg-white/80 shadow-[0_35px_35px_rgba(0,0,0,0.25)] backdrop-blur-md backdrop-brightness-75 group min-[320px]:grid-cols-1 min-[768px]:grid-cols-2 lg:hover:backdrop-brightness-90 hover:backdrop-blur-lg [&_a]:hover:underline [&>*]:p-4 [&_a]:text-blue-500 before:absolute before:inset-0 before:content-['']">
    <h1 class="grid-cols-[200px_1fr] p-[calc(1rem+2px)] shadow-[0_10px_20px_rgba(0,0,0,0.1)] backdrop-blur-md group-hover:bg-blue-500 [&>*]:p-4 before:content-['']">Test</h1>
</div>
    """
    result = process_html(html, config)
    assert result == expected


def test_css_apply_advanced(config: Config) -> None:
    """Test CSS @apply with advanced features."""
    css = """
.advanced {
    @apply backdrop-blur-md before:content-[''] [&>*]:p-4
           grid-cols-[200px_1fr] p-[calc(1rem+2px)]
           group-hover:bg-blue-500 shadow-[0_10px_20px_rgba(0,0,0,0.1)];
}
#other {
    @apply group relative p-[calc(1rem+2px)] m-[clamp(1rem,5vw,3rem)]
            backdrop-blur-md backdrop-brightness-75 bg-white/80
            before:content-[''] before:absolute before:inset-0
            hover:backdrop-blur-lg lg:hover:backdrop-brightness-90
            [&>*]:p-4 [&_a]:text-blue-500 [&_a]:hover:underline
            grid grid-cols-[200px_1fr_2fr] gap-[calc(1rem+2px)]
            min-[320px]:grid-cols-1 min-[768px]:grid-cols-2
            shadow-[0_35px_35px_rgba(0,0,0,0.25)];
}
    """
    expected = """
.advanced {
    @apply grid-cols-[200px_1fr] p-[calc(1rem+2px)] shadow-[0_10px_20px_rgba(0,0,0,0.1)] backdrop-blur-md group-hover:bg-blue-500 [&>*]:p-4 before:content-[''];
}
#other {
    @apply grid grid-cols-[200px_1fr_2fr] relative gap-[calc(1rem+2px)] m-[clamp(1rem,5vw,3rem)] p-[calc(1rem+2px)] bg-white/80 shadow-[0_35px_35px_rgba(0,0,0,0.25)] backdrop-blur-md backdrop-brightness-75 group min-[320px]:grid-cols-1 min-[768px]:grid-cols-2 lg:hover:backdrop-brightness-90 hover:backdrop-blur-lg [&_a]:hover:underline [&>*]:p-4 [&_a]:text-blue-500 before:absolute before:inset-0 before:content-[''];
}
    """
    result = process_css(css, config)
    assert result == expected


def test_shuffle_is_working(config: Config) -> None:
    """Test that the shuffling is actually shuffling."""
    classes = [
        "p-2",
        "bg-white/50",
        "backdrop-blur-md",
        "backdrop-brightness-75",
        "backdrop-contrast-125",
        "hue-rotate",
    ]
    assert shuffle(classes) != classes


def test_template_syntax(config: Config) -> None:
    """Test that the template syntax is working."""


@pytest.mark.parametrize(
    "html",
    [
        '<div class="p-4 container {{ extra_classes }}"></div>',
        '<div class="{% if not active %}hidden{% endif %} p-4 container"></div>',
    ],
)
def test_template_markup_in_class(html: str, config: Config) -> None:
    """Classes with Template syntax should not be touched."""
    result = process_text(html, config)
    assert result == html


@pytest.mark.parametrize(
    ("html", "expected"),
    [
        (
            '<div class="p-4 container">{{ title }}</div>',
            '<div class="container p-4">{{ title }}</div>',
        ),
        (
            '{% if not active %}<div class="p-4 container">{{ title }}</div>{% endif %}',
            '{% if not active %}<div class="container p-4">{{ title }}</div>{% endif %}',
        ),
    ],
)
def test_template_markup_outside(html: str, expected: str, config: Config) -> None:
    """Template syntax is not in the classes, so this is sorted."""
    result = process_text(html, config)
    assert result == expected
