"""
Integration tests for the NEXUS AUTOPARTS console application.

Uses a MockRenderer that records draw calls instead of painting to a real
terminal, allowing end-to-end testing of the screen -> renderer pipeline
without curses.
"""

import pytest

from console.renderers.base import BaseRenderer
from console.core.navigation import Navigation
from console.db import Database


# =========================================================================
# MockRenderer
# =========================================================================

class MockRenderer(BaseRenderer):
    """A renderer that records all draw calls for later assertion.

    Pre-load ``self.keys`` with a sequence of key codes; ``get_key()``
    pops from the front and returns ESC (27) when the list is exhausted.
    """

    def __init__(self):
        self.calls = []          # list of (method_name, args, kwargs)
        self.keys = []           # pre-loaded key presses
        self._size = (24, 80)    # rows, cols

    # -- Lifecycle ----------------------------------------------------------

    def init_screen(self):
        self.calls.append(('init_screen', (), {}))

    def cleanup(self):
        self.calls.append(('cleanup', (), {}))

    # -- Screen queries -----------------------------------------------------

    def get_size(self):
        return self._size

    # -- Primitive operations -----------------------------------------------

    def clear(self):
        self.calls.append(('clear', (), {}))

    def refresh(self):
        self.calls.append(('refresh', (), {}))

    def get_key(self):
        if self.keys:
            return self.keys.pop(0)
        return 27  # ESC to exit

    # -- High-level widgets -------------------------------------------------

    def draw_header(self, title, subtitle=''):
        self.calls.append(('draw_header', (title, subtitle), {}))

    def draw_footer(self, key_labels):
        self.calls.append(('draw_footer', (key_labels,), {}))

    def draw_menu(self, items, selected_index=0, title=''):
        self.calls.append(('draw_menu', (items, selected_index), {}))

    def draw_table(self, headers, rows, widths, page_info=None,
                   selected_row=-1):
        self.calls.append(('draw_table', (headers, rows, widths), {}))

    def draw_detail(self, fields, title=''):
        self.calls.append(('draw_detail', (fields,), {}))

    def draw_form(self, fields, focused_index=0, title=''):
        self.calls.append(('draw_form', (fields, focused_index), {}))

    def draw_filter_list(self, items, filter_text, selected_index, title=''):
        self.calls.append(('draw_filter_list', (items, filter_text, selected_index), {}))

    def draw_comparison(self, columns, title=''):
        self.calls.append(('draw_comparison', (columns,), {}))

    # -- Low-level drawing --------------------------------------------------

    def draw_text(self, row, col, text, style='normal'):
        self.calls.append(('draw_text', (row, col, text, style), {}))

    def draw_box(self, top, left, height, width, title=''):
        self.calls.append(('draw_box', (top, left, height, width), {}))

    # -- Dialogs ------------------------------------------------------------

    def show_message(self, text, msg_type='info'):
        self.calls.append(('show_message', (text, msg_type), {}))
        if msg_type == 'confirm':
            return True  # auto-confirm
        return True

    def show_input(self, prompt, max_len=40):
        self.calls.append(('show_input', (prompt, max_len), {}))
        return None  # cancel by default

    # -- Helpers for assertions ---------------------------------------------

    def method_names(self):
        """Return a list of just the method names from recorded calls."""
        return [name for name, _args, _kwargs in self.calls]

    def calls_for(self, method_name):
        """Return only the calls matching *method_name*."""
        return [(args, kwargs) for name, args, kwargs in self.calls
                if name == method_name]


# =========================================================================
# Fixtures
# =========================================================================

@pytest.fixture
def mock_renderer():
    """Provide a fresh MockRenderer for each test."""
    return MockRenderer()


@pytest.fixture(scope="module")
def db():
    """Provide a shared Database instance for integration tests."""
    return Database()


# =========================================================================
# Test 1: App creates with screens
# =========================================================================

class TestAppCreatesWithScreens:
    def test_app_creates_with_screens(self, mock_renderer, db):
        """App should register at least 'menu' and 'estadisticas' screens."""
        from console.core.app import App
        app = App(renderer=mock_renderer, db=db)

        assert 'menu' in app.screens
        assert 'estadisticas' in app.screens
        assert len(app.screens) >= 2


# =========================================================================
# Test 2: App runs and quits
# =========================================================================

class TestAppRunsAndQuits:
    def test_app_runs_and_quits(self, mock_renderer, db):
        """Pre-load ESC + confirm-yes (ord('s')). App should exit cleanly."""
        from console.core.app import App

        # ESC triggers quit dialog, show_message auto-confirms True
        mock_renderer.keys = [27]  # ESC

        app = App(renderer=mock_renderer, db=db)
        app.run()  # should not raise

        # Verify init_screen and cleanup were both called
        names = mock_renderer.method_names()
        assert 'init_screen' in names
        assert 'cleanup' in names


# =========================================================================
# Test 3: Menu renders
# =========================================================================

class TestMenuRenders:
    def test_menu_renders(self, mock_renderer, db):
        """MenuPrincipalScreen.render() should call draw_header and draw_menu."""
        from console.screens.menu_principal import MenuPrincipalScreen

        screen = MenuPrincipalScreen()
        screen.render(context={}, db=db, renderer=mock_renderer)

        names = mock_renderer.method_names()
        assert 'draw_header' in names
        assert 'draw_menu' in names


# =========================================================================
# Test 4: Estadisticas renders
# =========================================================================

class TestEstadisticasRenders:
    def test_estadisticas_renders(self, mock_renderer, db):
        """EstadisticasScreen.render() should call draw_header and draw_text."""
        from console.screens.estadisticas import EstadisticasScreen

        screen = EstadisticasScreen()
        screen.render(context={}, db=db, renderer=mock_renderer)

        names = mock_renderer.method_names()
        assert 'draw_header' in names
        # EstadisticasScreen uses draw_text for its detail fields
        assert 'draw_text' in names


# =========================================================================
# Test 5: Navigation integration
# =========================================================================

class TestNavigationIntegration:
    def test_navigation_push_pop_breadcrumb(self):
        """Push menu, push estadisticas, verify breadcrumb, pop, verify current."""
        nav = Navigation()
        nav.push('menu', {}, label='Menu')
        nav.push('estadisticas', {}, label='Estadisticas')

        # Breadcrumb should show both
        assert nav.breadcrumb() == ['Menu', 'Estadisticas']
        assert nav.depth() == 2

        # Current should be estadisticas
        current = nav.current()
        assert current is not None
        assert current[0] == 'estadisticas'

        # Pop estadisticas
        popped = nav.pop()
        assert popped[0] == 'estadisticas'

        # Now current should be menu
        current = nav.current()
        assert current is not None
        assert current[0] == 'menu'
        assert nav.breadcrumb() == ['Menu']


# =========================================================================
# Test 6: All screens instantiate
# =========================================================================

class TestAllScreensInstantiate:
    """Import and instantiate all 13 screen classes, verifying each has
    name and title attributes."""

    # (module_path, class_name)
    _SCREEN_CLASSES = [
        ("console.screens.menu_principal",     "MenuPrincipalScreen"),
        ("console.screens.estadisticas",       "EstadisticasScreen"),
        ("console.screens.vehiculo_nav",       "VehiculoNavScreen"),
        ("console.screens.buscar_parte",       "BuscarParteScreen"),
        ("console.screens.buscar_texto",       "BuscarTextoScreen"),
        ("console.screens.vin_decoder",        "VinDecoderScreen"),
        ("console.screens.catalogo",           "CatalogoScreen"),
        ("console.screens.parte_detalle",      "ParteDetalleScreen"),
        ("console.screens.comparador",         "ComparadorScreen"),
        ("console.screens.admin_partes",       "AdminPartesScreen"),
        ("console.screens.admin_fabricantes",  "AdminFabricantesScreen"),
        ("console.screens.admin_crossref",     "AdminCrossrefScreen"),
        ("console.screens.admin_import",       "AdminImportScreen"),
    ]

    def test_all_13_screens_exist(self):
        """All 13 screen modules should be importable."""
        assert len(self._SCREEN_CLASSES) == 13

    @pytest.mark.parametrize("module_path,class_name", _SCREEN_CLASSES)
    def test_screen_instantiates(self, module_path, class_name):
        """Each screen class should instantiate and have name + title."""
        import importlib
        mod = importlib.import_module(module_path)
        cls = getattr(mod, class_name)

        instance = cls()
        assert hasattr(instance, 'name')
        assert hasattr(instance, 'title')
        assert isinstance(instance.name, str)
        assert isinstance(instance.title, str)
        assert len(instance.name) > 0
        assert len(instance.title) > 0
