"""Tests for the UI config flow"""

import pytest

from homeassistant import config_entries
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType


from custom_components.auth_oidc import DOMAIN
from custom_components.auth_oidc.config.const import (
    OIDC_PROVIDERS,
    CLIENT_ID,
    CLIENT_SECRET,
    DISCOVERY_URL,
    DISPLAY_NAME,
    FEATURES,
    FEATURES_AUTOMATIC_USER_LINKING,
    FEATURES_AUTOMATIC_PERSON_CREATION,
    FEATURES_INCLUDE_GROUPS_SCOPE,
    CLAIMS,
    CLAIMS_DISPLAY_NAME,
    CLAIMS_GROUPS,
    CLAIMS_USERNAME,
    ROLES,
    ROLE_ADMINS,
    ROLE_USERS,
)

from .mocks.oidc_server import MockOIDCServer, mock_oidc_responses

DEMO_CLIENT_ID = "testing_example_client_id"
DEMO_CLIENT_SECRET = "faz"
DEMO_ADMIN_ROLE = "boo"
DEMO_USER_ROLE = "far"


@pytest.mark.asyncio
async def test_full_config_flow_success(hass: HomeAssistant):
    """Test a successful full config flow."""

    with mock_oidc_responses():
        # 1. Start the user step
        # This simulates clicking "Add Integration" in the UI.
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )

        # Assert that it's a form and expects user input for the 'user' step
        # 'user' is always the first step if it is user triggered
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "user"
        assert result["data_schema"] is not None
        schema = result["data_schema"]
        # Extract the schema dict from voluptuous Schema
        schema_dict = schema.schema
        # Assert 'provider' is a key in the schema
        assert "provider" in schema_dict
        # Assert 'authentik' is one of the allowed values for 'provider'
        provider_field = schema_dict["provider"]
        # If provider_field is a voluptuous In validator, get its container
        allowed_providers = getattr(provider_field, "container", None)
        assert "authentik" in OIDC_PROVIDERS
        assert allowed_providers is not None and "authentik" in allowed_providers

        assert result["errors"] == {}

        # 2. Submit user input for the 'user' step
        # This simulates the user filling out host/port
        user_input_step_user = {"provider": "authentik"}
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input_step_user
        )

        # Assert that it proceeds to the 'auth' step
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "discovery_url"
        assert result["data_schema"] is not None
        assert result["errors"] == {}

        # Fill in the discovery URL
        user_input_step_discovery = {
            "discovery_url": MockOIDCServer.get_discovery_url()
        }
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input_step_discovery
        )

        # Assert that it proceeds to the 'credentials' step
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "validate_connection"

        # Assert that it validates correctly with our mock
        assert result["errors"] == {}

        # Send in continue
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"action": "continue"}
        )
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "client_config"
        assert result["data_schema"] is not None
        assert result["errors"] == {}

        # Fill in the client config
        user_input_step_client_config = {
            "client_id": DEMO_CLIENT_ID,
            "client_secret": DEMO_CLIENT_SECRET,
        }
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input_step_client_config
        )

        # Assert that we are at groups_config
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "groups_config"
        assert result["data_schema"] is not None
        assert result["errors"] == {}

        # Fill in the groups config
        user_input_step_groups_config = {
            "admin_group": DEMO_ADMIN_ROLE,
            "user_group": DEMO_USER_ROLE,
        }
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input_step_groups_config
        )

        # Assert that were are at user_linking config
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "user_linking"
        assert result["data_schema"] is not None
        assert result["errors"] == {}

        # Fill in the user linking config
        user_input_step_user_linking = {"enable_user_linking": False}
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input_step_user_linking
        )

        # Finally, assert that the flow is complete and a config entry is created
        assert result["type"] == FlowResultType.CREATE_ENTRY
        assert result["title"] == OIDC_PROVIDERS["authentik"]["name"]

        expected_data = {
            "provider": "authentik",
            CLIENT_ID: DEMO_CLIENT_ID,
            CLIENT_SECRET: DEMO_CLIENT_SECRET,
            DISCOVERY_URL: MockOIDCServer.get_discovery_url(),
            DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["name"],
            FEATURES: {
                FEATURES_AUTOMATIC_USER_LINKING: False,
                FEATURES_AUTOMATIC_PERSON_CREATION: True,
                FEATURES_INCLUDE_GROUPS_SCOPE: True,
            },
            CLAIMS: {
                CLAIMS_DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["claims"][
                    "display_name"
                ],
                CLAIMS_USERNAME: OIDC_PROVIDERS["authentik"]["claims"]["username"],
                CLAIMS_GROUPS: OIDC_PROVIDERS["authentik"]["claims"]["groups"],
            },
            ROLES: {ROLE_ADMINS: DEMO_ADMIN_ROLE, ROLE_USERS: DEMO_USER_ROLE},
        }

        assert result["data"] == expected_data

        # Verify that the config entry was loaded into Home Assistant
        entries = hass.config_entries.async_entries(DOMAIN)
        assert len(entries) == 1
        assert entries[0].data == expected_data


@pytest.mark.asyncio
async def test_options_flow_success(hass: HomeAssistant):
    """Test a successful options flow."""

    # First, set up an initial config entry as in the full config flow
    initial_data = {
        "provider": "authentik",
        CLIENT_ID: DEMO_CLIENT_ID,
        CLIENT_SECRET: DEMO_CLIENT_SECRET,
        DISCOVERY_URL: MockOIDCServer.get_discovery_url(),
        DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["name"],
        FEATURES: {
            FEATURES_AUTOMATIC_USER_LINKING: False,
            FEATURES_AUTOMATIC_PERSON_CREATION: True,
            FEATURES_INCLUDE_GROUPS_SCOPE: True,
        },
        CLAIMS: {
            CLAIMS_DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["claims"]["display_name"],
            CLAIMS_USERNAME: OIDC_PROVIDERS["authentik"]["claims"]["username"],
            CLAIMS_GROUPS: OIDC_PROVIDERS["authentik"]["claims"]["groups"],
        },
        ROLES: {ROLE_ADMINS: DEMO_ADMIN_ROLE, ROLE_USERS: DEMO_USER_ROLE},
    }

    entry = config_entries.ConfigEntry(
        version=1,
        minor_version=0,
        domain=DOMAIN,
        title=OIDC_PROVIDERS["authentik"]["name"],
        data=initial_data,
        source=config_entries.SOURCE_USER,
        entry_id="1",
        unique_id="test_unique_id",
        options={},
        pref_disable_new_entities=False,
        pref_disable_polling=False,
        discovery_keys=[],
        subentries_data=None,
    )

    await hass.config_entries.async_add(entry)

    # Start the reconfigure flow
    result = await hass.config_entries.options.async_init(entry.entry_id)

    # Should start the options flow
    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "init"
    assert result["data_schema"] is not None

    # Assert that the schema is as expected
    # Schema contains enable_user_linking, enable_groups, admin_group & user_groups and no other keys
    schema = result["data_schema"]
    schema_dict = schema.schema
    # Assert that the schema contains the expected keys
    expected_keys = {
        "admin_group",
        "enable_user_linking",
        "enable_groups",
        "user_group",
    }
    assert set(schema_dict.keys()) == expected_keys

    # Change the client_id and client_secret
    new_enable_linking = True
    new_enable_groups = True
    new_admin_group = "bazzbbb"
    new_user_group = "foobar"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        {
            "enable_user_linking": new_enable_linking,
            "enable_groups": new_enable_groups,
            "admin_group": new_admin_group,
            "user_group": new_user_group,
        },
    )

    # Should finish and update the entry options
    assert result["type"] == FlowResultType.CREATE_ENTRY

    # Optionally, check that the entry options are updated
    updated_entry = hass.config_entries.async_get_entry(entry.entry_id)
    assert updated_entry is not None

    # Verify that the config entry was loaded into Home Assistant
    entries = hass.config_entries.async_entries(DOMAIN)
    assert len(entries) == 1

    assert (
        entries[0].data[FEATURES][FEATURES_AUTOMATIC_USER_LINKING] == new_enable_linking
    )
    assert entries[0].data[FEATURES][FEATURES_INCLUDE_GROUPS_SCOPE] == new_enable_groups
    assert entries[0].data[ROLES][ROLE_ADMINS] == new_admin_group
    assert entries[0].data[ROLES][ROLE_USERS] == new_user_group


@pytest.mark.asyncio
async def test_reconfigure_flow_success(hass: HomeAssistant):
    """Test a successful reconfigure flow."""

    # First, set up an initial config entry as in the full config flow
    initial_data = {
        "provider": "authentik",
        CLIENT_ID: DEMO_CLIENT_ID,
        CLIENT_SECRET: DEMO_CLIENT_SECRET,
        DISCOVERY_URL: MockOIDCServer.get_discovery_url(),
        DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["name"],
        FEATURES: {
            FEATURES_AUTOMATIC_USER_LINKING: False,
            FEATURES_AUTOMATIC_PERSON_CREATION: True,
            FEATURES_INCLUDE_GROUPS_SCOPE: True,
        },
        CLAIMS: {
            CLAIMS_DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["claims"]["display_name"],
            CLAIMS_USERNAME: OIDC_PROVIDERS["authentik"]["claims"]["username"],
            CLAIMS_GROUPS: OIDC_PROVIDERS["authentik"]["claims"]["groups"],
        },
        ROLES: {ROLE_ADMINS: DEMO_ADMIN_ROLE, ROLE_USERS: DEMO_USER_ROLE},
    }

    entry = config_entries.ConfigEntry(
        version=1,
        minor_version=0,
        domain=DOMAIN,
        title=OIDC_PROVIDERS["authentik"]["name"],
        data=initial_data,
        source=config_entries.SOURCE_USER,
        entry_id="1",
        unique_id="test_unique_id",
        options={},
        pref_disable_new_entities=False,
        pref_disable_polling=False,
        discovery_keys=[],
        subentries_data=None,
    )

    await hass.config_entries.async_add(entry)

    # Start async_step_reconfigure to reconfigure the entry
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": config_entries.SOURCE_RECONFIGURE,
            "entry_id": entry.entry_id,
        },
    )

    # Should start the reconfigure flow
    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "reconfigure"
    assert result["data_schema"] is not None

    # Assert that the schema is client_id & client_secret
    schema = result["data_schema"]
    schema_dict = schema.schema
    # Assert that the schema contains the expected keys
    expected_keys = {
        "client_id",
        "client_secret",
    }
    assert set(schema_dict.keys()) == expected_keys

    # Change the client_id and client_secret
    new_client_id = "newclientid"
    new_client_secret = "newclientsecret"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {
            "client_id": new_client_id,
            "client_secret": new_client_secret,
        },
    )

    # Should finish and update the entry data
    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "reconfigure_successful"

    # Verify that the config entry was loaded into Home Assistant
    entries = hass.config_entries.async_entries(DOMAIN)
    assert len(entries) == 1
    assert entries[0].data[CLIENT_ID] == new_client_id
    assert entries[0].data[CLIENT_SECRET] == new_client_secret


@pytest.mark.asyncio
async def test_reconfigure_flow_rejects_invalid_client_id(hass: HomeAssistant):
    """Reconfigure should keep the form open when the client ID is invalid."""
    initial_data = {
        "provider": "authentik",
        CLIENT_ID: DEMO_CLIENT_ID,
        CLIENT_SECRET: DEMO_CLIENT_SECRET,
        DISCOVERY_URL: MockOIDCServer.get_discovery_url(),
        DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["name"],
        FEATURES: {
            FEATURES_AUTOMATIC_USER_LINKING: False,
            FEATURES_AUTOMATIC_PERSON_CREATION: True,
            FEATURES_INCLUDE_GROUPS_SCOPE: True,
        },
        CLAIMS: {
            CLAIMS_DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["claims"]["display_name"],
            CLAIMS_USERNAME: OIDC_PROVIDERS["authentik"]["claims"]["username"],
            CLAIMS_GROUPS: OIDC_PROVIDERS["authentik"]["claims"]["groups"],
        },
        ROLES: {ROLE_ADMINS: DEMO_ADMIN_ROLE, ROLE_USERS: DEMO_USER_ROLE},
    }

    entry = config_entries.ConfigEntry(
        version=1,
        minor_version=0,
        domain=DOMAIN,
        title=OIDC_PROVIDERS["authentik"]["name"],
        data=initial_data,
        source=config_entries.SOURCE_USER,
        entry_id="1",
        unique_id="test_unique_id",
        options={},
        pref_disable_new_entities=False,
        pref_disable_polling=False,
        discovery_keys=[],
        subentries_data=None,
    )

    await hass.config_entries.async_add(entry)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": config_entries.SOURCE_RECONFIGURE,
            "entry_id": entry.entry_id,
        },
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"client_id": "   ", "client_secret": DEMO_CLIENT_SECRET},
    )

    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "reconfigure"
    assert result["errors"]["client_id"] == "invalid_client_id"


@pytest.mark.asyncio
async def test_reconfigure_flow_keeps_client_secret_when_blank(hass: HomeAssistant):
    """Submitting a blank secret should keep the existing client secret."""
    initial_data = {
        "provider": "authentik",
        CLIENT_ID: DEMO_CLIENT_ID,
        CLIENT_SECRET: DEMO_CLIENT_SECRET,
        DISCOVERY_URL: MockOIDCServer.get_discovery_url(),
        DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["name"],
        FEATURES: {
            FEATURES_AUTOMATIC_USER_LINKING: False,
            FEATURES_AUTOMATIC_PERSON_CREATION: True,
            FEATURES_INCLUDE_GROUPS_SCOPE: True,
        },
        CLAIMS: {
            CLAIMS_DISPLAY_NAME: OIDC_PROVIDERS["authentik"]["claims"]["display_name"],
            CLAIMS_USERNAME: OIDC_PROVIDERS["authentik"]["claims"]["username"],
            CLAIMS_GROUPS: OIDC_PROVIDERS["authentik"]["claims"]["groups"],
        },
        ROLES: {ROLE_ADMINS: DEMO_ADMIN_ROLE, ROLE_USERS: DEMO_USER_ROLE},
    }

    entry = config_entries.ConfigEntry(
        version=1,
        minor_version=0,
        domain=DOMAIN,
        title=OIDC_PROVIDERS["authentik"]["name"],
        data=initial_data,
        source=config_entries.SOURCE_USER,
        entry_id="1",
        unique_id="test_unique_id",
        options={},
        pref_disable_new_entities=False,
        pref_disable_polling=False,
        discovery_keys=[],
        subentries_data=None,
    )

    await hass.config_entries.async_add(entry)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": config_entries.SOURCE_RECONFIGURE,
            "entry_id": entry.entry_id,
        },
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"client_id": DEMO_CLIENT_ID, "client_secret": ""},
    )

    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "reconfigure_successful"

    updated_entry = hass.config_entries.async_get_entry(entry.entry_id)
    assert updated_entry is not None
    assert updated_entry.data[CLIENT_SECRET] == DEMO_CLIENT_SECRET


@pytest.mark.asyncio
async def test_validation_actions_route_to_other_steps(hass: HomeAssistant):
    """Validation actions should route to the requested flow step."""
    with mock_oidc_responses():
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"provider": "authentik"}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"discovery_url": MockOIDCServer.get_discovery_url()},
        )

        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "validate_connection"

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"action": "fix_discovery"}
        )
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "discovery_url"

    with mock_oidc_responses():
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"provider": "authentik"}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"discovery_url": MockOIDCServer.get_discovery_url()},
        )

        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "validate_connection"

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"action": "change_provider"}
        )

        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "user"


@pytest.mark.asyncio
async def test_user_flow_aborts_when_yaml_configured(hass: HomeAssistant):
    """The user flow should abort when YAML config already owns the provider."""
    hass.data[DOMAIN] = {"yaml_config": {"client_id": DEMO_CLIENT_ID}}

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "yaml_configured"


@pytest.mark.asyncio
async def test_user_flow_aborts_when_entry_already_exists(hass: HomeAssistant):
    """The flow should not create a second OIDC config entry."""
    entry = config_entries.ConfigEntry(
        version=1,
        minor_version=0,
        domain=DOMAIN,
        title=OIDC_PROVIDERS["authentik"]["name"],
        data={"provider": "authentik"},
        source=config_entries.SOURCE_USER,
        entry_id="1",
        unique_id="test_unique_id",
        options={},
        pref_disable_new_entities=False,
        pref_disable_polling=False,
        discovery_keys=[],
        subentries_data=None,
    )
    await hass.config_entries.async_add(entry)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "single_instance_allowed"


@pytest.mark.asyncio
async def test_discovery_url_validation_rejects_invalid_url(hass: HomeAssistant):
    """Discovery URL validation should reject malformed inputs."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"provider": "authentik"}
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"discovery_url": "not-a-valid-oidc-url"}
    )

    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "discovery_url"
    assert result["errors"]["discovery_url"] == "invalid_url_format"


@pytest.mark.asyncio
async def test_generic_provider_skips_groups_config(hass: HomeAssistant):
    """Providers without group support should go straight to user linking."""
    with mock_oidc_responses():
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"provider": "generic"}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"discovery_url": MockOIDCServer.get_discovery_url()},
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"action": "continue"}
        )
        assert result["step_id"] == "client_config"

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"client_id": DEMO_CLIENT_ID, "client_secret": DEMO_CLIENT_SECRET},
        )

    assert result["type"] == FlowResultType.FORM
    assert result["step_id"] == "user_linking"


@pytest.mark.asyncio
async def test_groups_disabled_skips_roles_and_creates_entry(hass: HomeAssistant):
    """Disabling groups should skip role configuration and omit roles from entry data."""
    with mock_oidc_responses():
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"provider": "authentik"}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"discovery_url": MockOIDCServer.get_discovery_url()},
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"action": "continue"}
        )
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"client_id": DEMO_CLIENT_ID, "client_secret": DEMO_CLIENT_SECRET},
        )
        assert result["step_id"] == "groups_config"

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"enable_groups": False}
        )
        assert result["step_id"] == "user_linking"

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"], {"enable_user_linking": True}
        )

    assert result["type"] == FlowResultType.CREATE_ENTRY
    assert "roles" not in result["data"]
    assert result["data"][FEATURES][FEATURES_INCLUDE_GROUPS_SCOPE] is False
