"""Provider settings: a choice among named options, not an on/off switch.

None of these choices does anything yet -- see the module docstring. These
tests cover the storage/validation shape only.
"""
from unittest.mock import MagicMock

import pytest

from app.services.chat.tools_settings import (
    FEATURE_DEFAULTS, PROVIDER_DEFAULTS, PROVIDER_OPTIONS, PROVIDER_REQUIRES_FEATURE,
    apply_over_defaults, save_tool_settings,
)


def _mock_store(monkeypatch, initial=None):
    """A fake settings store: no real database, so tests stay isolated."""
    stored = {"value": initial}

    def fake_update_setting(tenant_id, key, fn):
        stored["value"] = fn(stored["value"])
        return stored["value"]

    def fake_get_tool_settings(tenant_id):
        return apply_over_defaults(stored["value"])

    monkeypatch.setattr(
        "app.services.chat.tools_settings.update_setting", fake_update_setting)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.read_legacy_blob", lambda t: None)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_tool_settings", fake_get_tool_settings)
    return stored


def test_a_fresh_tenant_gets_the_real_defaults():
    settings = apply_over_defaults(None)
    assert settings["providers"] == PROVIDER_DEFAULTS


def test_ticket_management_defaults_to_the_backend_already_live():
    # create_or_update_ticket already exists and works; a new tenant should not
    # look unconfigured for something that is already running.
    assert PROVIDER_DEFAULTS["ticket_management"] == "galaxiq_ticketmanagement"


def test_chat_conversation_has_no_default_channel():
    assert PROVIDER_DEFAULTS["chat_conversation"] is None


def test_a_legacy_stored_blob_with_no_providers_key_does_not_crash():
    # Pre-migration tenants' stored blob predates this key entirely.
    settings = apply_over_defaults({"features": {"send_email": False}, "email": {}})
    assert settings["providers"] == PROVIDER_DEFAULTS


def test_a_stored_option_outside_the_current_list_is_dropped_not_surfaced():
    # An option since removed from PROVIDER_OPTIONS must not leak through as a
    # value nothing in the code recognises.
    settings = apply_over_defaults({"providers": {"chat_conversation": "telegram"}})
    assert settings["providers"]["chat_conversation"] is None


def test_a_valid_stored_option_is_kept():
    settings = apply_over_defaults({"providers": {"chat_conversation": "slack"}})
    assert settings["providers"]["chat_conversation"] == "slack"


def test_save_rejects_an_option_not_on_the_list(monkeypatch):
    with pytest.raises(ValueError, match="chat_conversation must be one of"):
        save_tool_settings("org_provider_test", providers={"chat_conversation": "telegram"})


def test_save_rejects_an_unknown_setting_name():
    with pytest.raises(ValueError, match="Unknown provider setting"):
        save_tool_settings("org_provider_test", providers={"not_a_real_setting": "x"})


def test_save_round_trips_a_real_choice(monkeypatch):
    _mock_store(monkeypatch)

    # chat_conversation is gated (see the gate tests below), so enabling it
    # and choosing a channel happen in the same call here.
    saved = save_tool_settings("org_provider_test",
                               features={"chat_conversation": True},
                               providers={"chat_conversation": "whatsapp"})
    assert saved["providers"]["chat_conversation"] == "whatsapp"
    # ticket_management untouched by this call, still its default.
    assert saved["providers"]["ticket_management"] == "galaxiq_ticketmanagement"


def test_save_clears_a_choice_back_to_unset(monkeypatch):
    _mock_store(monkeypatch, initial={
        "features": {"chat_conversation": True}, "email": {},
        "providers": {"chat_conversation": "slack",
                      "ticket_management": "galaxiq_ticketmanagement"}})

    saved = save_tool_settings("org_provider_test", providers={"chat_conversation": None})
    assert saved["providers"]["chat_conversation"] is None


def test_every_option_set_is_a_tuple_not_a_mutable_list():
    # These are fixed at the code level -- a tenant or a stored row must never
    # be able to grow this list at runtime.
    for options in PROVIDER_OPTIONS.values():
        assert isinstance(options, tuple)


# --- the gate: a provider that requires its feature switched on -------------

def test_chat_conversation_is_off_by_default():
    # Unlike the eight pre-existing switches (all default on to preserve prior
    # behaviour), this one is new and unwired -- nothing to preserve.
    assert FEATURE_DEFAULTS["chat_conversation"] is False


def test_selecting_a_channel_while_the_feature_is_off_is_refused(monkeypatch):
    _mock_store(monkeypatch)
    with pytest.raises(ValueError, match="chat_conversation cannot be set while"):
        save_tool_settings("org_gate_test", providers={"chat_conversation": "whatsapp"})


def test_enabling_and_selecting_in_the_same_call_is_allowed(monkeypatch):
    _mock_store(monkeypatch)
    saved = save_tool_settings("org_gate_test",
                               features={"chat_conversation": True},
                               providers={"chat_conversation": "whatsapp"})
    assert saved["features"]["chat_conversation"] is True
    assert saved["providers"]["chat_conversation"] == "whatsapp"


def test_selecting_is_allowed_once_already_enabled_in_an_earlier_call(monkeypatch):
    _mock_store(monkeypatch, initial={
        "features": {"chat_conversation": True}, "email": {}, "providers": {}})
    saved = save_tool_settings("org_gate_test", providers={"chat_conversation": "slack"})
    assert saved["providers"]["chat_conversation"] == "slack"


def test_clearing_the_channel_back_to_none_is_never_gated(monkeypatch):
    # A tenant must always be able to unset a channel, even while the feature
    # that would have used it is off -- only choosing a real value is gated.
    _mock_store(monkeypatch)
    saved = save_tool_settings("org_gate_test", providers={"chat_conversation": None})
    assert saved["providers"]["chat_conversation"] is None


def test_ticket_management_has_no_gate():
    # Its provider already reflects a real, live backend regardless of the
    # switch's state, so there is nothing to require it be "on" for.
    assert "ticket_management" not in PROVIDER_REQUIRES_FEATURE


def test_turning_the_feature_off_does_not_reject_the_switch_itself(monkeypatch):
    _mock_store(monkeypatch, initial={
        "features": {"chat_conversation": True}, "email": {},
        "providers": {"chat_conversation": "whatsapp"}})
    # Only setting a NEW provider value while off is refused -- flipping the
    # switch off is always allowed, and leaves the prior channel in place
    # rather than silently clearing it.
    saved = save_tool_settings("org_gate_test", features={"chat_conversation": False})
    assert saved["features"]["chat_conversation"] is False
    assert saved["providers"]["chat_conversation"] == "whatsapp"


# --- send_email: gated on a real connection, not another feature switch -----

def test_send_email_defaults_to_smtp():
    assert PROVIDER_DEFAULTS["send_email"] == "smtp"


def test_send_email_is_not_in_the_feature_gate_map():
    # Its gate is a live external fact (an active Mailchimp connection), not
    # another setting in this same blob -- see the mailchimp-specific check.
    assert "send_email" not in PROVIDER_REQUIRES_FEATURE


def test_selecting_mailchimp_without_a_connection_is_refused(monkeypatch):
    _mock_store(monkeypatch)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_mailchimp_connection", lambda t: None)
    with pytest.raises(ValueError, match="Connect Mailchimp under Integrations"):
        save_tool_settings("org_provider_test", providers={"send_email": "mailchimp"})


def test_selecting_mailchimp_with_an_active_connection_is_allowed(monkeypatch):
    _mock_store(monkeypatch)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_mailchimp_connection",
        lambda t: {"transactional_api_key": "md-abc"})
    saved = save_tool_settings("org_provider_test", providers={"send_email": "mailchimp"})
    assert saved["providers"]["send_email"] == "mailchimp"


def test_selecting_smtp_never_checks_for_a_connection(monkeypatch):
    _mock_store(monkeypatch)
    get_mailchimp_connection = MagicMock()
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_mailchimp_connection", get_mailchimp_connection)
    saved = save_tool_settings("org_provider_test", providers={"send_email": "smtp"})
    assert saved["providers"]["send_email"] == "smtp"
    get_mailchimp_connection.assert_not_called()


def test_clearing_send_email_back_to_none_never_checks_for_a_connection(monkeypatch):
    _mock_store(monkeypatch, initial={
        "features": {}, "email": {}, "providers": {"send_email": "mailchimp"}})
    get_mailchimp_connection = MagicMock()
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_mailchimp_connection", get_mailchimp_connection)
    saved = save_tool_settings("org_provider_test", providers={"send_email": None})
    assert saved["providers"]["send_email"] is None
    get_mailchimp_connection.assert_not_called()


# --- product_recommendation: shopify, ungated ---------------------------------

def test_product_recommendation_defaults_to_unset():
    assert PROVIDER_DEFAULTS["product_recommendation"] is None


def test_product_recommendation_has_no_gate():
    # Unlike chat_conversation, selecting a source here doesn't require the
    # product_recommendation switch to already be on -- same as ticket_management.
    assert "product_recommendation" not in PROVIDER_REQUIRES_FEATURE


def test_save_accepts_shopify_for_product_recommendation(monkeypatch):
    _mock_store(monkeypatch)
    saved = save_tool_settings("org_provider_test", providers={"product_recommendation": "shopify"})
    assert saved["providers"]["product_recommendation"] == "shopify"


def test_save_rejects_an_unknown_product_recommendation_option(monkeypatch):
    _mock_store(monkeypatch)
    with pytest.raises(ValueError, match="product_recommendation must be one of"):
        save_tool_settings("org_provider_test", providers={"product_recommendation": "csv"})


# --- an unselected dashboard dropdown submits "", not null --------------------

def test_an_empty_string_provider_value_is_treated_as_unset(monkeypatch):
    # Confirmed live: turning chat_conversation on without picking a channel
    # sends providers.chat_conversation="" -- this must succeed and clear the
    # choice, not be rejected as an invalid enum value.
    _mock_store(monkeypatch)
    saved = save_tool_settings("org_provider_test",
                               features={"chat_conversation": True},
                               providers={"chat_conversation": ""})
    assert saved["providers"]["chat_conversation"] is None


def test_a_whitespace_only_provider_value_is_treated_as_unset(monkeypatch):
    _mock_store(monkeypatch)
    saved = save_tool_settings("org_provider_test",
                               features={"chat_conversation": True},
                               providers={"chat_conversation": "   "})
    assert saved["providers"]["chat_conversation"] is None


def test_a_real_provider_value_is_unaffected_by_the_blank_normalisation(monkeypatch):
    _mock_store(monkeypatch)
    saved = save_tool_settings("org_provider_test",
                               features={"chat_conversation": True},
                               providers={"chat_conversation": "whatsapp"})
    assert saved["providers"]["chat_conversation"] == "whatsapp"


# --- book_meeting: gated on a real connection (google_calendar or teams) ------

def test_book_meeting_defaults_to_unset():
    # Like product_recommendation: nothing is pre-selected, since neither
    # Google Calendar nor Teams is inherently "the" default the way SMTP is
    # for send_email.
    settings = apply_over_defaults(None)
    assert settings["providers"]["book_meeting"] is None


def test_book_meeting_is_now_wired_to_a_real_tool():
    from app.services.chat.tools_settings import FEATURE_TOOLS
    assert FEATURE_TOOLS["book_meeting"] == "book_meeting"


def test_selecting_google_calendar_without_a_connection_is_refused(monkeypatch):
    _mock_store(monkeypatch)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_google_calendar_connection",
        lambda t: None)
    with pytest.raises(ValueError, match="Connect Google Calendar under Integrations"):
        save_tool_settings("org_provider_test_bm", providers={"book_meeting": "google_calendar"})


def test_selecting_google_calendar_with_an_active_connection_is_allowed(monkeypatch):
    _mock_store(monkeypatch)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_google_calendar_connection",
        lambda t: {"access_token": "tok", "calendar_email": "a@b.com", "timezone": "UTC"})
    saved = save_tool_settings("org_provider_test_bm", providers={"book_meeting": "google_calendar"})
    assert saved["providers"]["book_meeting"] == "google_calendar"


def test_selecting_teams_without_a_connection_is_refused(monkeypatch):
    _mock_store(monkeypatch)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_teams_connection", lambda t: None)
    with pytest.raises(ValueError, match="Connect Microsoft Teams under Integrations"):
        save_tool_settings("org_provider_test_bm", providers={"book_meeting": "teams"})


def test_selecting_teams_with_an_active_connection_is_allowed(monkeypatch):
    _mock_store(monkeypatch)
    monkeypatch.setattr(
        "app.services.chat.tools_settings.get_teams_connection",
        lambda t: {"access_token": "tok", "user_email": "a@b.com", "timezone": "UTC"})
    saved = save_tool_settings("org_provider_test_bm", providers={"book_meeting": "teams"})
    assert saved["providers"]["book_meeting"] == "teams"
