"""Tests for PSRAM component."""

from typing import Any

import pytest

from esphome.components.esp32 import (
    KEY_VARIANT,
    VARIANT_ESP32,
    VARIANT_ESP32C2,
    VARIANT_ESP32C3,
    VARIANT_ESP32C5,
    VARIANT_ESP32C6,
    VARIANT_ESP32H2,
    VARIANT_ESP32P4,
    VARIANT_ESP32S2,
    VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import CONF_ESPHOME, PlatformFramework
from tests.component_tests.types import SetCoreConfigCallable

UNSUPPORTED_PSRAM_VARIANTS = [
    VARIANT_ESP32C2,
    VARIANT_ESP32C3,
    VARIANT_ESP32C6,
    VARIANT_ESP32H2,
]

SUPPORTED_PSRAM_VARIANTS = [
    VARIANT_ESP32,
    VARIANT_ESP32C5,
    VARIANT_ESP32P4,
    VARIANT_ESP32S2,
    VARIANT_ESP32S3,
]
SUPPORTED_PSRAM_MODES = {
    VARIANT_ESP32: ["quad"],
    VARIANT_ESP32C5: ["quad"],
    VARIANT_ESP32P4: ["hex"],
    VARIANT_ESP32S2: ["quad"],
    VARIANT_ESP32S3: ["quad", "octal"],
}


@pytest.mark.parametrize(
    ("config", "error_match"),
    [
        pytest.param(
            {},
            r"PSRAM is not supported on this chip",
            id="psram_not_supported",
        ),
    ],
)
@pytest.mark.parametrize("variant", UNSUPPORTED_PSRAM_VARIANTS)
def test_psram_configuration_errors_unsupported_variants(
    config: Any,
    error_match: str,
    variant: str,
    set_core_config: SetCoreConfigCallable,
) -> None:
    set_core_config(
        PlatformFramework.ESP32_IDF,
        platform_data={KEY_VARIANT: variant},
        full_config={CONF_ESPHOME: {}},
    )
    """Test detection of invalid PSRAM configuration on unsupported variants."""
    from esphome.components.psram import CONFIG_SCHEMA

    with pytest.raises(cv.Invalid, match=error_match):
        CONFIG_SCHEMA(config)


@pytest.mark.parametrize("variant", SUPPORTED_PSRAM_VARIANTS)
def test_psram_configuration_valid_supported_variants(
    variant: str,
    set_core_config: SetCoreConfigCallable,
) -> None:
    set_core_config(
        PlatformFramework.ESP32_IDF,
        platform_data={KEY_VARIANT: variant},
        full_config={
            CONF_ESPHOME: {},
            "esp32": {
                "variant": variant,
                "cpu_frequency": "160MHz",
                "framework": {"type": "esp-idf"},
            },
        },
    )
    """Test that PSRAM configuration is valid on supported variants."""
    from esphome.components.psram import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA

    # This should not raise an exception
    config = CONFIG_SCHEMA({"mode": SUPPORTED_PSRAM_MODES[variant][0]})
    FINAL_VALIDATE_SCHEMA(config)


def _setup_psram_final_validation_test(
    esp32_config: dict,
    set_core_config: SetCoreConfigCallable,
    set_component_config: Any,
) -> str:
    """Helper function to set up ESP32 configuration for PSRAM final validation tests."""
    # Use ESP32S3 for schema validation to allow all options, then override for final validation
    schema_variant = "ESP32S3"
    final_variant = esp32_config.get("variant", "ESP32S3")
    full_esp32_config = {
        "variant": final_variant,
        "cpu_frequency": esp32_config.get("cpu_frequency", "240MHz"),
        "framework": {"type": "esp-idf"},
    }

    set_core_config(
        PlatformFramework.ESP32_IDF,
        platform_data={KEY_VARIANT: schema_variant},
        full_config={
            CONF_ESPHOME: {},
            "esp32": full_esp32_config,
        },
    )
    set_component_config("esp32", full_esp32_config)

    return final_variant


@pytest.mark.parametrize(
    ("config", "esp32_config", "expect_error", "error_match"),
    [
        pytest.param(
            {"mode": "quad", "speed": "120MHz"},
            {"cpu_frequency": "160MHz"},
            True,
            r"PSRAM 120MHz requires 240MHz CPU frequency",
            id="120mhz_requires_240mhz_cpu",
        ),
        pytest.param(
            {"mode": "octal"},
            {"variant": "ESP32"},
            True,
            r"Octal PSRAM is only supported on ESP32-S3",
            id="octal_mode_only_esp32s3",
        ),
        pytest.param(
            {"mode": "quad", "enable_ecc": True},
            {},
            True,
            r"ECC is only available in octal mode",
            id="ecc_only_in_octal_mode",
        ),
        pytest.param(
            {"mode": "quad", "speed": "120MHZ"},
            {"cpu_frequency": "240MHZ"},
            False,
            None,
            id="120mhz_with_240mhz_cpu",
        ),
        pytest.param(
            {"mode": "octal"},
            {"variant": "ESP32S3"},
            False,
            None,
            id="octal_mode_on_esp32s3",
        ),
        pytest.param(
            {"mode": "octal", "enable_ecc": True},
            {"variant": "ESP32S3"},
            False,
            None,
            id="ecc_in_octal_mode",
        ),
    ],
)
def test_psram_final_validation(
    config: Any,
    esp32_config: dict,
    expect_error: bool,
    error_match: str | None,
    set_core_config: SetCoreConfigCallable,
    set_component_config: Any,
) -> None:
    """Test PSRAM final validation for both error and valid cases."""
    from esphome.components.psram import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
    from esphome.core import CORE

    final_variant = _setup_psram_final_validation_test(
        esp32_config, set_core_config, set_component_config
    )

    validated_config = CONFIG_SCHEMA(config)

    # Update CORE variant for final validation
    CORE.data["esp32"][KEY_VARIANT] = final_variant

    if expect_error:
        with pytest.raises(cv.Invalid, match=error_match):
            FINAL_VALIDATE_SCHEMA(validated_config)
    else:
        # This should not raise an exception
        FINAL_VALIDATE_SCHEMA(validated_config)
