"""
conftest.py

Purpose:
  Pytest configuration and fixtures for the automated testing system.
  Provides isolated test environments with in-memory databases, temporary
  directories, and pre-configured test users and clients.

Fixtures:
  - app: Flask application instance configured for testing
  - client: Flask test client for HTTP requests
  - db: In-memory SQLite database with test schema
  - temp_static: Temporary directory for image file testing
  - test_users: Pre-created test users with different permission levels
  - authenticated_clients: Pre-authenticated test clients
"""

import os
import tempfile
import sqlite3
import pytest
import bcrypt
from flask import Flask
from app_modules.initializer import create_app
from app_modules.db import get_db


@pytest.fixture(scope='function')
def app():
    """Create and configure a Flask app instance for testing."""
    # Store original environment variables
    original_env = {}
    env_keys = ['DATABASE_PATH', 'USER_PHOTOS_DIR_REAL', 'SECRET_KEY', 'ENABLE_HSTS']
    for key in env_keys:
        original_env[key] = os.environ.get(key)
    
    # Create temporary database file
    db_fd, db_path = tempfile.mkstemp()
    
    # Create temporary static directory
    temp_static = tempfile.mkdtemp()
    temp_user_photos = os.path.join(temp_static, 'User-photos')
    os.makedirs(temp_user_photos, exist_ok=True)
    
    # Set environment variables for testing
    os.environ['DATABASE_PATH'] = db_path
    os.environ['USER_PHOTOS_DIR_REAL'] = temp_user_photos
    os.environ['SECRET_KEY'] = 'test-secret-key-for-testing-only'
    os.environ['ENABLE_HSTS'] = '0'  # Disable HSTS for testing
    
    # Configure rate limiting for testing (effectively disabled)
    os.environ['LOGIN_MAX_FAILS'] = '10000'  # Effectively disable rate limiting
    os.environ['LOGIN_WINDOW_SECONDS'] = '1'  # Very short window
    os.environ['LOGIN_LOCK_SECONDS'] = '1'  # Very short lock time
    
    # Create Flask app
    app = create_app()
    app.config['TESTING'] = True
    app.config['WTF_CSRF_ENABLED'] = False  # Disable CSRF for easier testing
    
    # Initialize database schema
    with app.app_context():
        init_test_database()
    
    yield app
    
    # Clear Flask g object to ensure database connection is not cached
    with app.app_context():
        from flask import g
        if hasattr(g, '_database'):
            g._database.close()
            delattr(g, '_database')
    
    # Cleanup
    os.close(db_fd)
    os.unlink(db_path)
    import shutil
    shutil.rmtree(temp_static, ignore_errors=True)
    
    # Restore original environment variables
    for key in env_keys:
        if original_env[key] is not None:
            os.environ[key] = original_env[key]
        else:
            os.environ.pop(key, None)


@pytest.fixture(scope='function')
def client(app):
    """Create a Flask test client."""
    return app.test_client()


@pytest.fixture(scope='function')
def db(app):
    """Get database connection for the test app."""
    with app.app_context():
        yield get_db()


@pytest.fixture(scope='function')
def temp_static(app):
    """Get temporary static directory path."""
    return os.environ.get('USER_PHOTOS_DIR_REAL')


def init_test_database():
    """Initialize the test database with the required schema."""
    # Use direct connection instead of get_db() to avoid caching issues
    db_path = os.environ.get('DATABASE_PATH')
    db = sqlite3.connect(db_path)
    db.row_factory = sqlite3.Row
    
    # Create users table
    db.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password_hash TEXT NOT NULL,
            is_admin BOOLEAN NOT NULL DEFAULT 0
        )
    ''')
    
    # Create cameras table
    db.execute('''
        CREATE TABLE IF NOT EXISTS cameras (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            camera_id INTEGER NOT NULL,
            camera_name TEXT NOT NULL,
            file_paths TEXT,
            added_to_map INTEGER DEFAULT 0,
            location TEXT,
            FOREIGN KEY (user_id) REFERENCES users (id)
        )
    ''')
    
    # Create markers table
    db.execute('''
        CREATE TABLE IF NOT EXISTS markers (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            type TEXT NOT NULL,
            camera_id TEXT,
            latitude REAL NOT NULL,
            longitude REAL NOT NULL,
            added_to_map INTEGER DEFAULT 0,
            name TEXT,
            FOREIGN KEY (user_id) REFERENCES users (id)
        )
    ''')
    
    # Create login_attempts table
    db.execute('''
        CREATE TABLE IF NOT EXISTS login_attempts (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT NOT NULL,
            ip TEXT NOT NULL,
            fail_count INTEGER DEFAULT 0,
            last_failed_at INTEGER,
            locked_until INTEGER
        )
    ''')
    
    # Create login_ip_attempts table
    db.execute('''
        CREATE TABLE IF NOT EXISTS login_ip_attempts (
            ip TEXT PRIMARY KEY,
            fail_count INTEGER DEFAULT 0,
            last_failed_at INTEGER,
            locked_until INTEGER
        )
    ''')
    
    # Create auth_log table
    db.execute('''
        CREATE TABLE IF NOT EXISTS auth_log (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ts INTEGER NOT NULL,
            ip TEXT NOT NULL,
            user_id INTEGER,
            username TEXT,
            event TEXT NOT NULL,
            detail TEXT,
            FOREIGN KEY (user_id) REFERENCES users (id)
        )
    ''')
    
    # Create admin_audit table
    db.execute('''
        CREATE TABLE IF NOT EXISTS admin_audit (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ts INTEGER NOT NULL,
            ip TEXT NOT NULL,
            admin_user_id INTEGER NOT NULL,
            target_user_id INTEGER,
            action TEXT NOT NULL,
            detail TEXT,
            FOREIGN KEY (admin_user_id) REFERENCES users (id),
            FOREIGN KEY (target_user_id) REFERENCES users (id)
        )
    ''')
    
    db.commit()
    db.close()


@pytest.fixture(scope='function')
def test_users(app):
    """Create test users with different permission levels."""
    with app.app_context():
        # Use direct connection to avoid caching issues
        db_path = os.environ.get('DATABASE_PATH')
        db = sqlite3.connect(db_path)
        db.row_factory = sqlite3.Row
        users = {}
        
        # Regular user
        regular_password_hash = bcrypt.hashpw('testpass123'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
        cursor = db.execute(
            'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)',
            ('testuser', regular_password_hash, False)
        )
        users['regular'] = {
            'id': cursor.lastrowid,
            'username': 'testuser',
            'password': 'testpass123',
            'is_admin': False
        }
        
        # Admin user
        admin_password_hash = bcrypt.hashpw('adminpass123'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
        cursor = db.execute(
            'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)',
            ('testadmin', admin_password_hash, True)
        )
        users['admin'] = {
            'id': cursor.lastrowid,
            'username': 'testadmin',
            'password': 'adminpass123',
            'is_admin': True
        }
        
        # Limited user (for testing user isolation)
        limited_password_hash = bcrypt.hashpw('limitedpass123'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
        cursor = db.execute(
            'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?)',
            ('limiteduser', limited_password_hash, False)
        )
        users['limited'] = {
            'id': cursor.lastrowid,
            'username': 'limiteduser',
            'password': 'limitedpass123',
            'is_admin': False
        }
        
        db.commit()
        db.close()
        return users


@pytest.fixture(scope='function')
def authenticated_client_regular(client, test_users):
    """Create an authenticated test client for regular user."""
    user = test_users['regular']
    client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    return client


@pytest.fixture(scope='function')
def authenticated_client_admin(client, test_users):
    """Create an authenticated test client for admin user."""
    user = test_users['admin']
    client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    return client


@pytest.fixture(scope='function')
def authenticated_client_limited(client, test_users):
    """Create an authenticated test client for limited user."""
    user = test_users['limited']
    client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    return client


@pytest.fixture(scope='function')
def sample_camera_data():
    """Provide sample camera data for testing."""
    return {
        'valid': {
            'camera_id': '123456789012',
            'camera_name': 'Test Camera 1'
        },
        'valid_2': {
            'camera_id': '987654321098',
            'camera_name': 'Test Camera 2'
        },
        'invalid_short_id': {
            'camera_id': '12345',
            'camera_name': 'Invalid Camera'
        },
        'invalid_non_numeric': {
            'camera_id': '12345678901a',
            'camera_name': 'Invalid Camera'
        },
        'invalid_empty_name': {
            'camera_id': '123456789012',
            'camera_name': ''
        }
    }


@pytest.fixture(scope='function')
def sample_image_data():
    """Provide sample image data for testing."""
    return {
        'jpg': {
            'filename': 'PICT_20240101_120000_123456789012.jpg',
            'content_type': 'image/jpeg',
            'data': b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb'
        },
        'png': {
            'filename': 'IMG_20240101_130000_123456789012.png',
            'content_type': 'image/png',
            'data': b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01'
        }
    }


@pytest.fixture(scope='function')
def sample_marker_data():
    """Provide sample marker data for testing."""
    return {
        'camera_marker': {
            'type': 'camera',
            'camera_id': '123456789012',
            'latitude': 45.123456,
            'longitude': 19.654321,
            'name': 'Test Camera Marker'
        },
        'fauna_marker': {
            'type': 'fauna',
            'latitude': 45.234567,
            'longitude': 19.765432,
            'name': 'Wild Boar Sighting'
        },
        'structure_marker': {
            'type': 'structure',
            'latitude': 45.345678,
            'longitude': 19.876543,
            'name': 'Feeding Station'
        }
    }


def clear_rate_limit_data(app):
    """Helper function to clear rate limiting data."""
    with app.app_context():
        db_path = os.environ.get('DATABASE_PATH')
        db = sqlite3.connect(db_path)
        db.row_factory = sqlite3.Row
        
        # Clear rate limiting tables
        db.execute('DELETE FROM login_attempts')
        db.execute('DELETE FROM login_ip_attempts')
        db.commit()
        db.close()


@pytest.fixture(scope='function')
def clear_rate_limits(app):
    """Clear rate limiting data before and after each test."""
    # Clear at the beginning of each test
    clear_rate_limit_data(app)
    
    # Provide the clear function for mid-test use
    def clear_now():
        clear_rate_limit_data(app)
    
    yield clear_now
    
    # Clean up after test
    clear_rate_limit_data(app)