"""Tests for user settings endpoints."""
from __future__ import annotations

import json
from unittest.mock import MagicMock

import pytest
from fastapi.testclient import TestClient

from app.cloud_session import get_session
from app.db.session import get_store
from app.main import app
from app.models.schemas.recipe import RecipeRequest
from app.services.recipe.recipe_engine import RecipeEngine

client = TestClient(app)


def _make_session(tier: str = "free", has_byok: bool = False) -> MagicMock:
    mock = MagicMock()
    mock.tier = tier
    mock.has_byok = has_byok
    return mock


def _make_store() -> MagicMock:
    mock = MagicMock()
    mock.get_setting.return_value = None
    mock.set_setting.return_value = None
    mock.search_recipes_by_ingredients.return_value = []
    mock.check_and_increment_rate_limit.return_value = (True, 1)
    return mock


@pytest.fixture()
def tmp_store() -> MagicMock:
    session_mock = _make_session()
    store_mock = _make_store()
    app.dependency_overrides[get_session] = lambda: session_mock
    app.dependency_overrides[get_store] = lambda: store_mock
    yield store_mock
    app.dependency_overrides.clear()


def test_set_and_get_cooking_equipment(tmp_store: MagicMock) -> None:
    """PUT then GET round-trips the cooking_equipment value."""
    equipment_json = '["oven", "stovetop"]'

    # PUT stores the value
    put_resp = client.put(
        "/api/v1/settings/cooking_equipment",
        json={"value": equipment_json},
    )
    assert put_resp.status_code == 200
    assert put_resp.json()["key"] == "cooking_equipment"
    assert put_resp.json()["value"] == equipment_json
    tmp_store.set_setting.assert_called_once_with("cooking_equipment", equipment_json)

    # GET returns the stored value
    tmp_store.get_setting.return_value = equipment_json
    get_resp = client.get("/api/v1/settings/cooking_equipment")
    assert get_resp.status_code == 200
    assert get_resp.json()["value"] == equipment_json


def test_get_missing_setting_returns_404(tmp_store: MagicMock) -> None:
    """GET an allowed key that was never set returns 404."""
    tmp_store.get_setting.return_value = None
    resp = client.get("/api/v1/settings/cooking_equipment")
    assert resp.status_code == 404


def test_hard_day_mode_uses_equipment_setting(tmp_store: MagicMock) -> None:
    """RecipeEngine.suggest() respects cooking_equipment from store when hard_day_mode=True."""
    equipment_json = '["microwave"]'
    tmp_store.get_setting.return_value = equipment_json

    engine = RecipeEngine(store=tmp_store)
    req = RecipeRequest(
        pantry_items=["rice", "water"],
        level=1,
        constraints=[],
        hard_day_mode=True,
    )

    result = engine.suggest(req)

    # Engine should have read the equipment setting
    tmp_store.get_setting.assert_any_call("cooking_equipment")
    # Result is a valid RecipeResult (no crash)
    assert result is not None
    assert hasattr(result, "suggestions")


def test_put_unknown_key_returns_422(tmp_store: MagicMock) -> None:
    """PUT to an unknown settings key returns 422."""
    resp = client.put(
        "/api/v1/settings/nonexistent_key",
        json={"value": "something"},
    )
    assert resp.status_code == 422


def test_put_null_value_returns_422(tmp_store: MagicMock) -> None:
    """PUT with a null value returns 422 (Pydantic validation)."""
    resp = client.put(
        "/api/v1/settings/cooking_equipment",
        json={"value": None},
    )
    assert resp.status_code == 422


def test_set_and_get_sensory_preferences(tmp_store: MagicMock) -> None:
    """PUT then GET round-trips the sensory_preferences value."""
    prefs = json.dumps({
        "avoid_textures": ["mushy", "slimy"],
        "max_smell": "pungent",
        "max_noise": "loud",
    })

    put_resp = client.put(
        "/api/v1/settings/sensory_preferences",
        json={"value": prefs},
    )
    assert put_resp.status_code == 200
    assert put_resp.json()["key"] == "sensory_preferences"
    tmp_store.set_setting.assert_called_with("sensory_preferences", prefs)

    tmp_store.get_setting.return_value = prefs
    get_resp = client.get("/api/v1/settings/sensory_preferences")
    assert get_resp.status_code == 200
    assert get_resp.json()["value"] == prefs


def test_sensory_preferences_unknown_key_still_422(tmp_store: MagicMock) -> None:
    """Confirm unknown keys still 422 after adding sensory_preferences."""
    resp = client.put(
        "/api/v1/settings/sensory_taste_buds",
        json={"value": "{}"},
    )
    assert resp.status_code == 422


def test_set_and_get_time_first_layout(tmp_store: MagicMock) -> None:
    """PUT then GET round-trips the time_first_layout value."""
    layout_value = "time_first"

    put_resp = client.put(
        "/api/v1/settings/time_first_layout",
        json={"value": layout_value},
    )
    assert put_resp.status_code == 200
    assert put_resp.json()["key"] == "time_first_layout"
    assert put_resp.json()["value"] == layout_value
    tmp_store.set_setting.assert_called_with("time_first_layout", layout_value)

    tmp_store.get_setting.return_value = layout_value
    get_resp = client.get("/api/v1/settings/time_first_layout")
    assert get_resp.status_code == 200
    assert get_resp.json()["value"] == layout_value


def test_time_first_layout_unknown_key_still_422(tmp_store: MagicMock) -> None:
    """Confirm unknown keys still 422 after adding time_first_layout."""
    resp = client.put(
        "/api/v1/settings/time_first_mode",
        json={"value": "time_first"},
    )
    assert resp.status_code == 422
