"""Flask application configuration."""

import os
from datetime import timedelta

from sqlalchemy.pool import StaticPool


class ConfigError(Exception):
    """Raised when required configuration is missing or unsafe."""


def _required_env(varname):
    """Read an env var; raise ConfigError if missing or empty."""
    value = os.environ.get(varname)
    if not value:
        raise ConfigError(
            f'{varname} is required in production. Set it in the environment '
            f'before starting the app. Insecure defaults are not permitted in '
            f'ProductionConfig.'
        )
    return value


class Config:
    """Base configuration."""

    SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')

    SQLALCHEMY_DATABASE_URI = os.environ.get(
        'DATABASE_URL',
        'mysql+pymysql://root:password@localhost:3306/shopdb_flask',
    )
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_pre_ping': True,
        'pool_recycle': 300,
        # Pin the client connection to utf8mb4 so reads/writes match the
        # utf8mb4 schema regardless of the server default charset. TestingConfig
        # (SQLite) replaces this whole dict, so the charset arg never reaches it.
        'connect_args': {'charset': 'utf8mb4'},
    }

    JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY', 'jwt-secret-key-change-in-production')
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(
        seconds=int(os.environ.get('JWT_ACCESS_TOKEN_EXPIRES', 3600))
    )
    JWT_REFRESH_TOKEN_EXPIRES = timedelta(
        seconds=int(os.environ.get('JWT_REFRESH_TOKEN_EXPIRES', 2592000))
    )

    CORS_ORIGINS = [
        origin.strip()
        for origin in os.environ.get('CORS_ORIGINS', 'http://localhost:5173').split(',')
        if origin.strip()
    ]

    LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')

    # API key for the unattended PowerShell collector scripts
    COLLECTOR_API_KEY = os.environ.get('COLLECTOR_API_KEY', '')

    # Trusted plugin publisher public keys (ADR-013). os.pathsep-separated PEM
    # file paths, delivered out-of-band with the deployed config - NEVER read
    # from the plugin shelf. Used to verify signed plugin artifacts. Empty on a
    # site that does not adopt marketplace plugins.
    PLUGIN_TRUSTED_KEYS = [
        path.strip()
        for path in os.environ.get('PLUGIN_TRUSTED_KEYS', '').split(os.pathsep)
        if path.strip()
    ]

    # When true, a plugin only loads/migrates if its tree matches a trusted
    # signature (verify-at-load, verify-at-migrate). Default false = existing
    # behavior. Turn on only after stamping plugins + pinning keys.
    PLUGIN_REQUIRE_SIGNED = os.environ.get(
        'PLUGIN_REQUIRE_SIGNED', 'false').lower() == 'true'

    # Directories whose unsigned plugins are trusted - honored ONLY under
    # DEBUG/TESTING (the external-repo/symlink dev workflow). Production ignores.
    PLUGIN_DEV_TRUST_DIRS = [
        path.strip()
        for path in os.environ.get('PLUGIN_DEV_TRUST_DIRS', '').split(os.pathsep)
        if path.strip()
    ]

    # Read-only folder the app pulls plugin artifacts from (a SharePoint-synced
    # or copied shelf). Empty = no shelf configured. The app never speaks any
    # network protocol; it reads this folder.
    PLUGIN_SHELF_DIR = os.environ.get('PLUGIN_SHELF_DIR', '')

    ZABBIX_ENABLED = os.environ.get('ZABBIX_ENABLED', 'false').lower() == 'true'
    ZABBIX_URL = os.environ.get('ZABBIX_URL', '')
    ZABBIX_TOKEN = os.environ.get('ZABBIX_TOKEN', '')

    # Read-only HR/employee directory database (separate from the app DB).
    # Credentials come from the environment; never hardcode them in source.
    # No safe default for the password: unset means empty, the connection
    # fails loud rather than silently trying a guessed credential.
    EMPLOYEE_DB_HOST = os.environ.get('EMPLOYEE_DB_HOST', 'localhost')
    EMPLOYEE_DB_USER = os.environ.get('EMPLOYEE_DB_USER', '')
    EMPLOYEE_DB_PASSWORD = os.environ.get('EMPLOYEE_DB_PASSWORD', '')
    EMPLOYEE_DB_NAME = os.environ.get('EMPLOYEE_DB_NAME', 'wjf_employees')

    # Read-write CMMC USB check-in/out database (separate from the app DB).
    # Credentials come from the environment; never hardcode them in source.
    # No safe default for the password: unset means empty, the connection
    # fails loud rather than silently trying a guessed credential.
    CMMC_USB_DB_HOST = os.environ.get('CMMC_USB_DB_HOST', 'localhost')
    CMMC_USB_DB_USER = os.environ.get('CMMC_USB_DB_USER', '')
    CMMC_USB_DB_PASSWORD = os.environ.get('CMMC_USB_DB_PASSWORD', '')
    CMMC_USB_DB_NAME = os.environ.get('CMMC_USB_DB_NAME', 'cmmc_usb')

    CACHE_TYPE = 'SimpleCache'
    CACHE_DEFAULT_TIMEOUT = 600

    # IP-based login rate limit (fixed window). Defense in depth atop the
    # per-account lockout. Backed by the existing cache extension.
    AUTH_RATELIMIT_ENABLED = os.environ.get(
        'AUTH_RATELIMIT_ENABLED', 'true').lower() == 'true'
    AUTH_RATELIMIT_MAX = int(os.environ.get('AUTH_RATELIMIT_MAX', 30))
    AUTH_RATELIMIT_WINDOW_SECONDS = int(
        os.environ.get('AUTH_RATELIMIT_WINDOW_SECONDS', 300))

    DEFAULT_PAGE_SIZE = 20
    MAX_PAGE_SIZE = 100


class DevelopmentConfig(Config):
    """Development configuration."""

    DEBUG = True
    SQLALCHEMY_ECHO = True

    SQLALCHEMY_DATABASE_URI = os.environ.get(
        'DATABASE_URL',
        'mysql+pymysql://root:rootpassword@127.0.0.1:3306/shopdb_flask',
    )


class TestingConfig(Config):
    """Testing configuration."""

    TESTING = True
    # Off by default so login-heavy fixtures do not trip the limiter; tests
    # that exercise rate limiting flip it on via app.config override.
    AUTH_RATELIMIT_ENABLED = False
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
    SQLALCHEMY_ENGINE_OPTIONS = {
        'connect_args': {'check_same_thread': False},
        'poolclass': StaticPool,
    }
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(seconds=5)


class ProductionConfig(Config):
    """Production configuration.

    Validation is deferred to validate() so that importing this class in a
    non-production environment (tests, dev, tooling) does not raise.
    create_app() invokes validate() when config_name == 'production' so
    a misconfigured production deploy still fails loud at boot.
    """

    DEBUG = False
    SQLALCHEMY_ECHO = False

    JWT_COOKIE_SECURE = True
    JWT_COOKIE_CSRF_PROTECT = True

    @classmethod
    def validate(cls):
        """Verify production config is safe. Called from create_app."""
        secret_key = os.environ.get('SECRET_KEY', '')
        jwt_secret = os.environ.get('JWT_SECRET_KEY', '')
        database_url = os.environ.get('DATABASE_URL', '')
        cors_raw = os.environ.get('CORS_ORIGINS', '').strip()

        insecure_defaults = {
            'dev-secret-key-change-in-production',
            'jwt-secret-key-change-in-production',
        }

        if not secret_key or secret_key in insecure_defaults:
            raise ConfigError(
                'SECRET_KEY is required in production and must not be the '
                'development default. Set a strong random value in the '
                'environment before starting the app.'
            )
        if not jwt_secret or jwt_secret in insecure_defaults:
            raise ConfigError(
                'JWT_SECRET_KEY is required in production and must not be '
                'the development default. Set a strong random value in the '
                'environment before starting the app.'
            )
        if not database_url:
            raise ConfigError(
                'DATABASE_URL is required in production. No fallback to a '
                'development localhost URL is permitted.'
            )
        if not cors_raw or cors_raw == '*':
            raise ConfigError(
                'CORS_ORIGINS must be a comma-separated allowlist of '
                'explicit origins in production. Wildcard "*" is not '
                'permitted. Example: '
                'CORS_ORIGINS=https://shopdb.example.com,https://shopdb-mirror.example.com'
            )


config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig,
}
