"""Tests for `flask plugin prune-schema` (ADR-014 schema-lean per-site).

The shared core Alembic baseline creates every plugin's tables, so a lean site
that omits a plugin still carries that plugin's (empty) tables. prune-schema
drops the tables of every not-installed plugin, leaving core + chosen-plugin
tables. These tests pin the three behaviors that matter: only not-installed
plugins are dropped, the non-empty guard refuses without --force, and a full
site (every plugin installed) is a no-op.
"""

import pytest
from sqlalchemy import inspect, text

from shopdb.extensions import db as _db
from shopdb.plugins.cli import plugin_cli
from shopdb.plugins.alembic_template import PLUGIN_TABLE_OWNERS


def _install_only(app, monkeypatch, installed_names):
    """Force discover_available to report exactly `installed_names` installed."""
    pm = app.extensions['plugin_manager']
    listing = [{'name': name, 'installed': name in installed_names}
               for name in PLUGIN_TABLE_OWNERS]
    monkeypatch.setattr(pm, 'discover_available', lambda: listing)


def test_prune_drops_only_not_installed(app, db, runner, monkeypatch):
    """A lean site keeps its plugins' tables and drops every other plugin's."""
    _install_only(app, monkeypatch, {'machines', 'printers'})
    before = set(inspect(_db.engine).get_table_names())
    assert 'usbdevices' in before and 'machines' in before  # baseline sanity

    result = runner.invoke(plugin_cli, ['prune-schema', '--yes'])
    assert result.exit_code == 0, result.output

    after = set(inspect(_db.engine).get_table_names())
    # Kept: machines + printers owned tables.
    for keep in PLUGIN_TABLE_OWNERS['machines'] + PLUGIN_TABLE_OWNERS['printers']:
        assert keep in after, f'{keep} should have been kept'
    # Dropped: every other plugin's tables.
    for plugin, tables in PLUGIN_TABLE_OWNERS.items():
        if plugin in ('machines', 'printers'):
            continue
        for tablename in tables:
            assert tablename not in after, f'{tablename} should have been dropped'
    # Core tables untouched.
    for core in ('assets', 'users', 'settings'):
        assert core in after


def test_prune_full_site_is_noop(app, db, runner, monkeypatch):
    """Every plugin installed -> nothing to prune, schema unchanged."""
    _install_only(app, monkeypatch, set(PLUGIN_TABLE_OWNERS))
    before = set(inspect(_db.engine).get_table_names())

    result = runner.invoke(plugin_cli, ['prune-schema', '--yes', '--force'])
    assert result.exit_code == 0, result.output
    assert 'Nothing to prune' in result.output
    assert set(inspect(_db.engine).get_table_names()) == before


def test_prune_refuses_non_empty_without_force(app, db, runner, monkeypatch):
    """A not-installed table that holds rows is not dropped without --force."""
    _install_only(app, monkeypatch, {'machines'})
    _db.session.execute(text(
        "INSERT INTO usbdevicetypes (typename, createddate, modifieddate, "
        "isactive) VALUES ('kept-by-guard', '2026-01-01', '2026-01-01', 1)"))
    _db.session.commit()

    result = runner.invoke(plugin_cli, ['prune-schema', '--yes'])
    assert result.exit_code == 1
    assert 'REFUSING' in result.output
    # The row-bearing table survives the refusal.
    assert 'usbdevicetypes' in set(inspect(_db.engine).get_table_names())


def test_prune_force_drops_non_empty(app, db, runner, monkeypatch):
    """--force drops even a table that holds rows."""
    _install_only(app, monkeypatch, {'machines'})
    _db.session.execute(text(
        "INSERT INTO usbdevicetypes (typename, createddate, modifieddate, "
        "isactive) VALUES ('dropped-by-force', '2026-01-01', '2026-01-01', 1)"))
    _db.session.commit()

    result = runner.invoke(plugin_cli, ['prune-schema', '--yes', '--force'])
    assert result.exit_code == 0, result.output
    assert 'usbdevicetypes' not in set(inspect(_db.engine).get_table_names())
