"""Pytest configuration and fixtures for shopdb-flask.

Strategy: in-memory SQLite via StaticPool (configured in TestingConfig)
so the database is shared across the connection. Each test drops and
recreates the schema. Simple, totally isolated, fast enough for a small
schema. Switch to savepoint-per-test if test count grows past a few
hundred.
"""

import json
import os
from datetime import datetime, timezone
from pathlib import Path

import pytest
from werkzeug.security import generate_password_hash

# Force testing config before any shopdb import touches the env.
os.environ['FLASK_ENV'] = 'testing'

from shopdb import create_app
from shopdb.extensions import db as _db


def _bootstrap_plugin_registry():
    # Fresh clones (CI) have no instance/plugins.json - the registry is
    # gitignored dev state - so no plugins would load and every plugin-backed
    # test fails. Seed a registry enabling all bundled plugins. An existing
    # dev registry is left untouched.
    repo = Path(__file__).resolve().parent.parent
    state_file = repo / 'instance' / 'plugins.json'
    if state_file.exists():
        return
    stamp = datetime.now(timezone.utc).replace(tzinfo=None).isoformat()
    plugins = {}
    for manifest_path in sorted((repo / 'plugins').glob('*/manifest.json')):
        manifest = json.loads(manifest_path.read_text())
        plugins[manifest['name']] = {
            'name': manifest['name'],
            'version': manifest.get('version', '1.0.0'),
            'installed_at': stamp,
            'enabled': True,
            'migrations_applied': [],
            'config': {},
        }
    state_file.parent.mkdir(parents=True, exist_ok=True)
    state_file.write_text(json.dumps({'plugins': plugins}, indent=2))


_bootstrap_plugin_registry()


@pytest.fixture(scope='session')
def app():
    """Create the Flask application for the test session."""
    application = create_app('testing')
    return application


@pytest.fixture(scope='function')
def db(app):
    """Provide a fresh database per test. Drops and recreates schema each run."""
    with app.app_context():
        _db.create_all()
        yield _db
        _db.session.remove()
        _db.drop_all()


@pytest.fixture
def client(app):
    """Flask test client."""
    return app.test_client()


@pytest.fixture
def runner(app):
    """Flask CLI test runner."""
    return app.test_cli_runner()


@pytest.fixture
def admin_user(db):
    """Create an admin user for authenticated tests.

    The user has username 'testadmin' and password 'testpass'.
    """
    from shopdb.core.models import User, Role

    role = Role(rolename='admin', description='Administrator')
    db.session.add(role)
    db.session.flush()

    user = User(
        username='testadmin',
        email='admin@test.local',
        passwordhash=generate_password_hash('testpass'),
    )
    user.roles.append(role)
    db.session.add(user)
    db.session.commit()

    return user


@pytest.fixture
def auth_headers(client, admin_user):
    """Log in as admin_user and return Authorization headers."""
    response = client.post(
        '/api/auth/login',
        json={'username': 'testadmin', 'password': 'testpass'},
    )
    assert response.status_code == 200, f'Login failed: {response.get_json()}'
    payload = response.get_json()
    token = payload['data']['access_token']
    return {'Authorization': f'Bearer {token}'}


@pytest.fixture
def member_user(db):
    """Create an authenticated user with NO roles/permissions.

    Username 'testmember', password 'testpass'. Used to prove that
    authentication alone does not grant write access (authorization gating).
    """
    from shopdb.core.models import User

    user = User(
        username='testmember',
        email='member@test.local',
        passwordhash=generate_password_hash('testpass'),
    )
    db.session.add(user)
    db.session.commit()

    return user


@pytest.fixture
def member_headers(client, member_user):
    """Log in as member_user (no roles) and return Authorization headers."""
    response = client.post(
        '/api/auth/login',
        json={'username': 'testmember', 'password': 'testpass'},
    )
    assert response.status_code == 200, f'Login failed: {response.get_json()}'
    token = response.get_json()['data']['access_token']
    return {'Authorization': f'Bearer {token}'}
