from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
from typing import List
import os

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', case_sensitive=False, extra='ignore')
    APP_NAME: str = Field(default='Hotel Booking API', description='Application name')
    APP_VERSION: str = Field(default='1.0.0', description='Application version')
    ENVIRONMENT: str = Field(default='development', description='Environment: development, staging, production')
    DEBUG: bool = Field(default=False, description='Debug mode')
    API_V1_PREFIX: str = Field(default='/api/v1', description='API v1 prefix')
    HOST: str = Field(default='0.0.0.0', description='Server host. WARNING: 0.0.0.0 binds to all interfaces. Use 127.0.0.1 for development or specific IP for production.')  # nosec B104 # Acceptable default with validation warning in production
    PORT: int = Field(default=8000, description='Server port')
    DB_USER: str = Field(default='root', description='Database user')
    DB_PASS: str = Field(default='', description='Database password')
    DB_NAME: str = Field(default='hotel_db', description='Database name')
    DB_HOST: str = Field(default='localhost', description='Database host')
    DB_PORT: str = Field(default='3306', description='Database port')
    JWT_SECRET: str = Field(default='', description='JWT secret key - MUST be set via environment variable. Minimum 64 characters recommended for production.')
    JWT_ALGORITHM: str = Field(default='HS256', description='JWT algorithm')
    JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(default=30, description='JWT access token expiration in minutes')
    JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = Field(default=3, description='JWT refresh token expiration in days (reduced from 7 for better security)')
    MAX_LOGIN_ATTEMPTS: int = Field(default=5, description='Maximum failed login attempts before account lockout')
    ACCOUNT_LOCKOUT_DURATION_MINUTES: int = Field(default=30, description='Account lockout duration in minutes after max failed attempts')
    PASSWORD_EXPIRY_DAYS: int = Field(default=0, description='Password expiry in days (0 = no expiry, 90 = 90 days)')
    PASSWORD_HISTORY_COUNT: int = Field(default=5, description='Number of previous passwords to prevent reuse (0 = disabled)')
    PASSWORD_MIN_AGE_DAYS: int = Field(default=1, description='Minimum days before password can be changed again (0 = no minimum)')
    ENCRYPTION_KEY: str = Field(default='', description='Base64-encoded encryption key for data encryption at rest')
    CLIENT_URL: str = Field(default='http://localhost:5173', description='Frontend client URL')
    CORS_ORIGINS: List[str] = Field(default_factory=lambda: ['http://localhost:5173', 'http://localhost:3000', 'http://127.0.0.1:5173'], description='Allowed CORS origins')
    RATE_LIMIT_ENABLED: bool = Field(default=True, description='Enable rate limiting')
    RATE_LIMIT_PER_MINUTE: int = Field(default=60, description='Requests per minute per IP')
    RATE_LIMIT_ADMIN_PER_MINUTE: int = Field(default=300, description='Requests per minute for admin users')
    RATE_LIMIT_STAFF_PER_MINUTE: int = Field(default=200, description='Requests per minute for staff users')
    RATE_LIMIT_ACCOUNTANT_PER_MINUTE: int = Field(default=200, description='Requests per minute for accountant users')
    RATE_LIMIT_CUSTOMER_PER_MINUTE: int = Field(default=100, description='Requests per minute for customer users')
    CSRF_PROTECTION_ENABLED: bool = Field(default=True, description='Enable CSRF protection')
    HSTS_PRELOAD_ENABLED: bool = Field(default=False, description='Enable HSTS preload directive (requires domain submission to hstspreload.org)')
    LOG_LEVEL: str = Field(default='INFO', description='Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL')
    LOG_FILE: str = Field(default='logs/app.log', description='Log file path')
    LOG_MAX_BYTES: int = Field(default=10485760, description='Max log file size (10MB)')
    LOG_BACKUP_COUNT: int = Field(default=5, description='Number of backup log files')
    SMTP_HOST: str = Field(default='smtp.gmail.com', description='SMTP host')
    SMTP_PORT: int = Field(default=587, description='SMTP port')
    SMTP_USER: str = Field(default='', description='SMTP username')
    SMTP_PASSWORD: str = Field(default='', description='SMTP password')
    SMTP_FROM_EMAIL: str = Field(default='', description='From email address')
    SMTP_FROM_NAME: str = Field(default='Hotel Booking', description='From name')
    UPLOAD_DIR: str = Field(default='uploads', description='Upload directory')
    MAX_UPLOAD_SIZE: int = Field(default=5242880, description='Max upload size in bytes (5MB)')
    MAX_REQUEST_BODY_SIZE: int = Field(default=10485760, description='Max request body size in bytes (10MB)')
    ALLOWED_EXTENSIONS: List[str] = Field(default_factory=lambda: ['jpg', 'jpeg', 'png', 'gif', 'webp'], description='Allowed file extensions')
    REDIS_ENABLED: bool = Field(default=False, description='Enable Redis caching')
    REDIS_HOST: str = Field(default='localhost', description='Redis host')
    REDIS_PORT: int = Field(default=6379, description='Redis port')
    REDIS_DB: int = Field(default=0, description='Redis database number')
    REDIS_PASSWORD: str = Field(default='', description='Redis password')
    REQUEST_TIMEOUT: int = Field(default=30, description='Request timeout in seconds')
    HEALTH_CHECK_INTERVAL: int = Field(default=30, description='Health check interval in seconds')
    STRIPE_SECRET_KEY: str = Field(default='', description='Stripe secret key')
    STRIPE_PUBLISHABLE_KEY: str = Field(default='', description='Stripe publishable key')
    STRIPE_WEBHOOK_SECRET: str = Field(default='', description='Stripe webhook secret')
    PAYPAL_CLIENT_ID: str = Field(default='', description='PayPal client ID')
    PAYPAL_CLIENT_SECRET: str = Field(default='', description='PayPal client secret')
    PAYPAL_MODE: str = Field(default='sandbox', description='PayPal mode: sandbox or live')
    BORICA_TERMINAL_ID: str = Field(default='', description='Borica Terminal ID')
    BORICA_MERCHANT_ID: str = Field(default='', description='Borica Merchant ID')
    BORICA_PRIVATE_KEY_PATH: str = Field(default='', description='Borica private key file path')
    BORICA_CERTIFICATE_PATH: str = Field(default='', description='Borica certificate file path')
    BORICA_GATEWAY_URL: str = Field(default='https://3dsgate-dev.borica.bg/cgi-bin/cgi_link', description='Borica gateway URL (test or production)')
    BORICA_MODE: str = Field(default='test', description='Borica mode: test or production')

    @property
    def database_url(self) -> str:
        """Generate database URL with proper credential escaping to prevent injection."""
        from urllib.parse import quote_plus
        # Properly escape credentials to handle special characters
        user = quote_plus(self.DB_USER)
        password = quote_plus(self.DB_PASS)
        host = quote_plus(self.DB_HOST)
        port = str(self.DB_PORT)
        name = quote_plus(self.DB_NAME)
        return f'mysql+pymysql://{user}:{password}@{host}:{port}/{name}'

    @property
    def is_production(self) -> bool:
        return self.ENVIRONMENT.lower() == 'production'

    @property
    def is_development(self) -> bool:
        return self.ENVIRONMENT.lower() == 'development'

    @property
    def redis_url(self) -> str:
        if self.REDIS_PASSWORD:
            return f'redis://:{self.REDIS_PASSWORD}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}'
        return f'redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}'
    
    IP_WHITELIST_ENABLED: bool = Field(default=False, description='Enable IP whitelisting for admin endpoints')
    ADMIN_IP_WHITELIST: List[str] = Field(default_factory=list, description='List of allowed IP addresses/CIDR ranges for admin endpoints')
    
    def validate_host_configuration(self) -> None:
        """
        Validate HOST configuration for security.
        Warns if binding to all interfaces (0.0.0.0) in production.
        """
        if self.HOST == '0.0.0.0' and self.is_production:
            import logging
            logger = logging.getLogger(__name__)
            logger.warning(
                'SECURITY WARNING: HOST is set to 0.0.0.0 in production. '
                'This binds the server to all network interfaces. '
                'Consider using a specific IP address or ensure proper firewall rules are in place.'
            )
    
    def validate_encryption_key(self) -> None:
        """
        Validate encryption key is properly configured.
        Raises ValueError if key is missing or invalid in production.
        """
        if not self.ENCRYPTION_KEY:
            if self.is_production:
                raise ValueError(
                    'CRITICAL: ENCRYPTION_KEY is not configured in production. '
                    'Please set ENCRYPTION_KEY environment variable to a base64-encoded 32-byte key.'
                )
            else:
                # In development, warn but don't fail
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(
                    'ENCRYPTION_KEY is not configured. Encryption operations may fail. '
                    'Please set ENCRYPTION_KEY environment variable.'
                )
                return
        
        # Validate base64 encoding and key length (32 bytes = 44 base64 chars)
        try:
            import base64
            import re
            # Try to decode with automatic padding if needed
            key_str = self.ENCRYPTION_KEY.strip()
            
            # Check if string contains only valid base64 characters
            # Base64 characters: A-Z, a-z, 0-9, +, /, and = for padding
            base64_pattern = re.compile(r'^[A-Za-z0-9+/]*={0,2}$')
            if not base64_pattern.match(key_str):
                raise ValueError(
                    'ENCRYPTION_KEY contains invalid characters. '
                    'Base64 strings can only contain A-Z, a-z, 0-9, +, /, and = (for padding).'
                )
            
            # Add padding if needed (base64 strings must be multiple of 4)
            missing_padding = len(key_str) % 4
            if missing_padding:
                key_str += '=' * (4 - missing_padding)
            
            # Decode without strict validation since we already checked the pattern
            decoded = base64.b64decode(key_str)
            if len(decoded) != 32:
                raise ValueError(
                    f'ENCRYPTION_KEY must be a base64-encoded 32-byte key. '
                    f'Received {len(decoded)} bytes after decoding (expected 32 bytes).'
                )
        except ValueError as e:
            # Re-raise ValueError as-is (these are our validation errors)
            if self.is_production:
                raise ValueError(
                    f'Invalid ENCRYPTION_KEY format: {str(e)}. '
                    'Must be a valid base64-encoded 32-byte key. '
                    'Generate one using: python -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"'
                )
            else:
                import logging
                logger = logging.getLogger(__name__)
                # In development, clear invalid key so encryption service can generate a temporary one
                if self.ENCRYPTION_KEY.strip():
                    logger.info(
                        f'ENCRYPTION_KEY is invalid ({str(e)}). '
                        'Clearing it in development mode - encryption service will generate a temporary key. '
                        'For production, generate a valid key using: '
                        'python -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"'
                    )
                    # Clear the invalid key so encryption service can generate a temporary one
                    self.ENCRYPTION_KEY = ''
        except Exception as e:
            # Handle other exceptions (like binascii.Error from base64)
            if self.is_production:
                raise ValueError(
                    f'Invalid ENCRYPTION_KEY format: {str(e)}. '
                    'Must be a valid base64-encoded 32-byte key. '
                    'Generate one using: python -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"'
                )
            else:
                import logging
                logger = logging.getLogger(__name__)
                # In development, clear invalid key so encryption service can generate a temporary one
                if self.ENCRYPTION_KEY.strip():
                    logger.info(
                        f'ENCRYPTION_KEY is invalid ({str(e)}). '
                        'Clearing it in development mode - encryption service will generate a temporary key. '
                        'For production, generate a valid key using: '
                        'python -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"'
                    )
                    # Clear the invalid key so encryption service can generate a temporary one
                    self.ENCRYPTION_KEY = ''

settings = Settings()

# Validate JWT_SECRET on startup - fail fast if not configured
def validate_jwt_secret():
    """Validate JWT_SECRET is properly configured. Called on startup."""
    if not settings.JWT_SECRET or settings.JWT_SECRET.strip() == '':
        error_msg = (
            'CRITICAL SECURITY ERROR: JWT_SECRET is not configured. '
            'Please set JWT_SECRET environment variable to a secure random string. '
            'Minimum 64 characters recommended for production. '
            'Generate one using: python -c "import secrets; print(secrets.token_urlsafe(64))"'
        )
        import logging
        logger = logging.getLogger(__name__)
        logger.error(error_msg)
        if settings.is_production:
            raise ValueError(error_msg)
        else:
            logger.warning(
                'JWT_SECRET not configured. This will cause authentication to fail. '
                'Set JWT_SECRET environment variable before starting the application.'
            )
    
    # Warn if using weak secret (less than 64 characters)
    if len(settings.JWT_SECRET) < 64:
        import logging
        logger = logging.getLogger(__name__)
        if settings.is_production:
            logger.warning(
                f'JWT_SECRET is only {len(settings.JWT_SECRET)} characters. '
                'Recommend using at least 64 characters for production security.'
            )
        else:
            logger.debug(f'JWT_SECRET length: {len(settings.JWT_SECRET)} characters')

# Validate on import
validate_jwt_secret()
settings.validate_host_configuration()