"""
La configuración de IA: un solo registro, y la herramienta tira de él.

Lo que se defiende es que ninguna constante siga siendo la fuente: lo que se
elige en «Config» es lo que llega al proveedor en la generación y en el
análisis, y lo que queda en `usage` es lo que se llamó. El proveedor se
sustituye con los mismos dobles que en `test_ai_services` y `test_ai_analysis`.
"""

import json
from types import SimpleNamespace

import pytest

from apps.ai import analysis, provider, services
from apps.ai.models import (
    AI_SETTINGS_ID,
    AiSettings,
    AnalysisState,
    GenerationState,
    PhotoAnalysis,
)
from tests.doubles import FakeResponses, vision_response
from tests.images import png_bytes
from tests.unit.test_ai_services import FakeImages, completed_response, queued

pytestmark = pytest.mark.django_db

PHOTO = png_bytes(64, 48)


@pytest.fixture
def media_root(settings, tmp_path):
    settings.AI_MEDIA_ROOT = tmp_path
    settings.OPENAI_API_KEY = 'sk-prueba'
    return tmp_path


class TestCurrent:
    def test_the_migration_leaves_the_row_created_with_the_values_of_the_v1(self):
        """R52: producción arranca configurada sin que nadie pase por la pantalla."""
        row = AiSettings.objects.get()

        assert row.pk == AI_SETTINGS_ID
        assert row.image_model == 'gpt-image-2.5-sunburst'
        assert row.image_quality == 'high'
        assert row.vision_model == 'gpt-5.4-mini'

    def test_without_the_row_it_is_born_again_with_the_values_of_the_v1(self):
        """La red para una base restaurada de antes de la migración: nunca un select vacío."""
        AiSettings.objects.all().delete()

        current = AiSettings.current()

        assert current.pk == AI_SETTINGS_ID
        assert current.image_model == 'gpt-image-2.5-sunburst'
        assert current.image_quality == 'high'
        assert current.vision_model == 'gpt-5.4-mini'

    def test_there_is_only_one_and_it_is_always_the_same(self):
        first = AiSettings.current()
        first.image_quality = 'max'
        first.save()

        second = AiSettings.current()

        assert second.pk == first.pk
        assert second.image_quality == 'max'
        assert AiSettings.objects.count() == 1

    def test_the_defaults_are_in_their_closed_lists(self):
        """Un valor inicial fuera de la lista dejaría la pantalla sin opción marcada."""
        option = provider.image_model(provider.DEFAULT_IMAGE_MODEL)

        assert option is not None
        assert option.admits(provider.DEFAULT_IMAGE_QUALITY)
        assert provider.DEFAULT_VISION_MODEL in provider.VISION_MODELS

    def test_every_model_admits_its_qualities_and_they_are_all_known(self):
        for option in provider.IMAGE_MODELS:
            assert option.qualities
            assert set(option.qualities) <= set(provider.IMAGE_QUALITIES)


class TestGenerationUsesTheConfiguration:
    def test_it_edits_with_the_configured_model_and_quality_and_records_them(
        self, media_root, monkeypatch
    ):
        images = FakeImages(completed_response())
        monkeypatch.setattr(services.provider, 'client', lambda: SimpleNamespace(images=images))
        current = AiSettings.current()
        current.image_model = 'gpt-image-2.5-flare'
        current.image_quality = 'max'
        current.save()
        source = media_root / 'sources' / 'patio.jpg'
        source.parent.mkdir(parents=True)
        source.write_bytes(b'foto')
        generation = queued(source_path='sources/patio.jpg')

        services.execute(generation)

        method, kwargs = images.calls[0]
        assert method == 'edit'
        assert kwargs['model'] == 'gpt-image-2.5-flare'
        assert kwargs['quality'] == 'max'
        generation.refresh_from_db()
        assert generation.state == GenerationState.COMPLETED
        assert generation.usage == {
            'model': 'gpt-image-2.5-flare',
            'quality': 'max',
            'total_tokens': 42,
        }

    def test_without_a_photo_the_generation_model_is_not_configurable(
        self, media_root, monkeypatch
    ):
        """El camino sin foto es de la v1 y de las muestras: no pasa por «Config»."""
        images = FakeImages(completed_response())
        monkeypatch.setattr(services.provider, 'client', lambda: SimpleNamespace(images=images))
        current = AiSettings.current()
        current.image_model = 'gpt-image-1'
        current.image_quality = 'low'
        current.save()

        services.execute(queued())

        method, kwargs = images.calls[0]
        assert method == 'generate'
        assert kwargs['model'] == provider.GENERATE_MODEL
        # La calidad sí es la configurada: es una sola para el módulo.
        assert kwargs['quality'] == 'low'


class TestAnalysisUsesTheConfiguration:
    def test_it_asks_the_configured_vision_model_and_records_it(self, media_root, monkeypatch):
        clean = json.dumps(
            {
                'areas_detected': ['driveway'],
                'pool_present': False,
                'confidence': {
                    'patio_deck': None,
                    'driveway': 0.9,
                    'entryway': None,
                    'walkway': None,
                    'pool_deck': None,
                    'pool': None,
                },
            }
        )
        responses = FakeResponses(vision_response(clean))
        monkeypatch.setattr(
            analysis.provider, 'client', lambda: SimpleNamespace(responses=responses)
        )
        current = AiSettings.current()
        current.vision_model = 'gpt-5.5'
        current.save()
        row = PhotoAnalysis()
        source = media_root / 'sources' / f'{row.id}.jpg'
        source.parent.mkdir(parents=True, exist_ok=True)
        source.write_bytes(PHOTO)
        row.source_path = f'sources/{row.id}.jpg'
        row.save()

        analysis.execute(row)

        assert responses.calls[0]['model'] == 'gpt-5.5'
        row.refresh_from_db()
        assert row.state == AnalysisState.COMPLETED
        assert row.usage == {'model': 'gpt-5.5', 'requests': [{'total_tokens': 42}]}
