"""
test_cameras.py

Purpose:
  Comprehensive test suite for camera management functionality including CRUD
  operations, data validation, API endpoint testing, and database consistency.
  Tests camera adding, renaming, deleting, listing, and user permission boundaries.
  
  This module implements red-team level security testing with comprehensive input
  validation and follows modular, well-organized patterns for production Apache2
  deployment scenarios. Each test ensures proper authentication, CSRF protection,
  and database state verification.

Test Categories:
  - Camera CRUD operations (add, rename, delete, list)
  - Data validation for camera IDs and names
  - API endpoint testing and responses
  - Database consistency verification
  - User permission boundaries and data isolation
  - Camera ID uniqueness validation
  - Authentication and CSRF protection
"""

import pytest
import json
import time
import random
from app_modules.db import get_db

# Global counter to ensure unique IDs across all tests
_test_counter = 0

def get_unique_camera_id():
    """Generate a unique 12-digit camera ID for testing."""
    global _test_counter
    _test_counter += 1
    # Use current time, counter, and random number to ensure uniqueness
    timestamp = int(time.time() * 1000) % 100000000  # 8 digits from timestamp
    counter_part = (_test_counter % 100)  # 2 digits from counter
    random_part = random.randint(10, 99)  # 2 digits random
    unique_id = f"{timestamp:08d}{counter_part:02d}{random_part:02d}"
    return unique_id[:12]  # Ensure exactly 12 digits


@pytest.mark.cameras
def test_add_camera_valid_data(client, test_users, sample_camera_data):
    """Test adding cameras with valid 12-digit IDs via POST /api/kamere/add."""
    user = test_users['regular']
    camera_data = sample_camera_data['valid']
    
    # Use unique camera ID to avoid conflicts
    unique_camera_id = get_unique_camera_id()
    
    # Manual login to ensure session is properly set up
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Make a GET request to ensure CSRF token is generated
    client.get('/')
    
    # Get CSRF token
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
        user_id = sess.get('user_id')
        assert user_id is not None, "User should be logged in"
    
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,
                              'camera_name': camera_data['camera_name']
                          },
                          content_type='application/json')
    
    # Should succeed with JSON response
    assert response.status_code == 200
    data = json.loads(response.data)
    assert data['success'] is True
    
    # Verify camera was added to database
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ? AND camera_name = ?',
            (unique_camera_id, camera_data['camera_name'])
        ).fetchone()
        assert camera is not None
        assert camera['camera_id'] == int(unique_camera_id)
        assert camera['camera_name'] == camera_data['camera_name']


@pytest.mark.cameras
def test_add_camera_invalid_short_id(client, test_users, sample_camera_data):
    """Test adding cameras with invalid short IDs returns appropriate errors."""
    user = test_users['regular']
    camera_data = sample_camera_data['invalid_short_id']
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': camera_data['camera_id'],
                              'camera_name': camera_data['camera_name']
                          },
                          content_type='application/json')
    
    # Should fail with error response
    assert response.status_code == 400
    data = json.loads(response.data)
    assert data['success'] is False
    assert 'znamenki' in data['message']  # Croatian error message about digits
    
    # Verify camera was NOT added to database
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (camera_data['camera_id'],)
        ).fetchone()
        assert camera is None


@pytest.mark.cameras
def test_add_camera_invalid_non_numeric_id(client, test_users, sample_camera_data):
    """Test adding cameras with invalid non-numeric IDs returns appropriate errors."""
    user = test_users['regular']
    camera_data = sample_camera_data['invalid_non_numeric']
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': camera_data['camera_id'],
                              'camera_name': camera_data['camera_name']
                          },
                          content_type='application/json')
    
    # Should fail with error response
    assert response.status_code == 400
    data = json.loads(response.data)
    assert data['success'] is False
    
    # Verify camera was NOT added to database
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_name = ?',
            (camera_data['camera_name'],)
        ).fetchone()
        assert camera is None


@pytest.mark.cameras
def test_add_camera_empty_name(client, test_users, sample_camera_data):
    """Test adding cameras with empty names returns appropriate errors."""
    user = test_users['regular']
    camera_data = sample_camera_data['invalid_empty_name']
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,
                              'camera_name': camera_data['camera_name']
                          },
                          content_type='application/json')
    
    # Should fail with error response
    assert response.status_code == 400
    data = json.loads(response.data)
    assert data['success'] is False
    assert 'obavezno' in data['message']  # Croatian error message about required field
    
    # Verify camera was NOT added to database
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (unique_camera_id,)
        ).fetchone()
        assert camera is None


@pytest.mark.cameras
def test_retrieve_camera_list_api(client, test_users, sample_camera_data):
    """Test retrieving camera lists via GET /api/kamere endpoint."""
    user = test_users['regular']
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    camera_data = sample_camera_data['valid']
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get actual user ID from session
    with client.session_transaction() as sess:
        user_id = sess.get('user_id')
        assert user_id is not None
    
    # Add a camera first
    with client.application.app_context():
        db = get_db()
        db.execute(
            'INSERT INTO cameras (user_id, camera_id, camera_name) VALUES (?, ?, ?)',
            (user_id, int(unique_camera_id), camera_data['camera_name'])
        )
        db.commit()
    
    # Retrieve camera list
    response = client.get('/api/kamere')
    assert response.status_code == 200
    
    # Should return JSON with camera data
    data = json.loads(response.data)
    assert 'cameras' in data
    cameras = data['cameras']
    assert isinstance(cameras, list)
    assert len(cameras) > 0
    
    # Find our camera in the response
    our_camera = None
    for camera in cameras:
        if camera['camera_id'] == unique_camera_id:  # API returns string camera_id
            our_camera = camera
            break
    
    assert our_camera is not None
    assert our_camera['camera_name'] == camera_data['camera_name']


@pytest.mark.cameras
def test_rename_existing_camera(client, test_users, sample_camera_data):
    """Test renaming existing cameras via POST /api/kamere/rename."""
    user = test_users['regular']
    camera_data = sample_camera_data['valid']
    new_name = "Renamed Test Camera"
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get user ID from session
    with client.session_transaction() as sess:
        user_id = sess.get('user_id')
        assert user_id is not None
    
    # Add a camera first
    with client.application.app_context():
        db = get_db()
        db.execute(
            'INSERT INTO cameras (user_id, camera_id, camera_name) VALUES (?, ?, ?)',
            (user_id, int(unique_camera_id), camera_data['camera_name'])
        )
        db.commit()
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    # Rename the camera (API expects 12-digit camera_id, not database id)
    response = client.post(f'/api/kamere/rename?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,
                              'camera_name': new_name
                          },
                          content_type='application/json')
    
    assert response.status_code == 200
    data = json.loads(response.data)
    assert data['success'] is True
    
    # Verify camera was renamed in database
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (int(unique_camera_id),)
        ).fetchone()
        assert camera is not None
        assert camera['camera_name'] == new_name


@pytest.mark.cameras
def test_delete_existing_camera(client, test_users, sample_camera_data):
    """Test deleting cameras via POST /api/kamere/delete."""
    user = test_users['regular']
    camera_data = sample_camera_data['valid']
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get user ID from session
    with client.session_transaction() as sess:
        user_id = sess.get('user_id')
        assert user_id is not None
    
    # Add a camera first
    with client.application.app_context():
        db = get_db()
        db.execute(
            'INSERT INTO cameras (user_id, camera_id, camera_name) VALUES (?, ?, ?)',
            (user_id, int(unique_camera_id), camera_data['camera_name'])
        )
        db.commit()
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    # Delete the camera (API expects 12-digit camera_id)
    response = client.post(f'/api/kamere/delete?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id
                          },
                          content_type='application/json')
    
    assert response.status_code == 200
    data = json.loads(response.data)
    assert data['success'] is True
    
    # Verify camera was deleted from database
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (int(unique_camera_id),)
        ).fetchone()
        assert camera is None


@pytest.mark.cameras
def test_database_cameras_match_api_responses(client, test_users, sample_camera_data):
    """Test database cameras match API responses consistency."""
    user = test_users['regular']
    
    # Use unique camera IDs
    unique_camera_id_1 = get_unique_camera_id()
    unique_camera_id_2 = get_unique_camera_id()
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get user ID from session
    with client.session_transaction() as sess:
        user_id = sess.get('user_id')
        assert user_id is not None
    
    # Add multiple cameras to database
    cameras_added = []
    with client.application.app_context():
        db = get_db()
        for i, (camera_id, camera_data) in enumerate([
            (unique_camera_id_1, sample_camera_data['valid']),
            (unique_camera_id_2, sample_camera_data['valid_2'])
        ]):
            db.execute(
                'INSERT INTO cameras (user_id, camera_id, camera_name) VALUES (?, ?, ?)',
                (user_id, int(camera_id), f"{camera_data['camera_name']} {i}")
            )
            cameras_added.append({
                'camera_id': int(camera_id),
                'camera_name': f"{camera_data['camera_name']} {i}"
            })
        db.commit()
    
    # Get cameras from API
    response = client.get('/api/kamere')
    assert response.status_code == 200
    api_data = json.loads(response.data)
    api_cameras = api_data['cameras']
    
    # Get cameras from database
    with client.application.app_context():
        db = get_db()
        db_cameras = db.execute(
            'SELECT * FROM cameras WHERE user_id = ?',
            (user_id,)
        ).fetchall()
    
    # Verify counts match
    assert len(api_cameras) == len(db_cameras)
    
    # Verify each camera from database appears in API response
    for db_camera in db_cameras:
        api_camera = None
        for cam in api_cameras:
            if cam['camera_id'] == str(db_camera['camera_id']):  # API returns string
                api_camera = cam
                break
        
        assert api_camera is not None
        assert api_camera['camera_name'] == db_camera['camera_name']


@pytest.mark.cameras
def test_user_isolation_cameras(client, test_users, sample_camera_data):
    """Test users can only manage their own cameras."""
    user_regular = test_users['regular']
    user_limited = test_users['limited']
    camera_data = sample_camera_data['valid']
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    
    # Login as regular user and add camera
    login_response = client.post('/login', data={
        'username': user_regular['username'],
        'password': user_regular['password']
    })
    assert login_response.status_code == 302
    
    # Get user ID from session
    with client.session_transaction() as sess:
        user_id = sess.get('user_id')
        assert user_id is not None
    
    # Add camera for regular user
    with client.application.app_context():
        db = get_db()
        db.execute(
            'INSERT INTO cameras (user_id, camera_id, camera_name) VALUES (?, ?, ?)',
            (user_id, int(unique_camera_id), camera_data['camera_name'])
        )
        db.commit()
    
    # Logout and login as limited user
    client.post('/logout', data={'csrf_token': ''})
    
    login_response = client.post('/login', data={
        'username': user_limited['username'],
        'password': user_limited['password']
    })
    assert login_response.status_code == 302
    
    # Limited user should not see regular user's cameras in API
    response = client.get('/api/kamere')
    assert response.status_code == 200
    limited_data = json.loads(response.data)
    limited_cameras = limited_data['cameras']
    
    # Should not contain regular user's camera
    for camera in limited_cameras:
        assert camera['camera_id'] != unique_camera_id  # API returns string
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    # Limited user should not be able to rename regular user's camera
    response = client.post(f'/api/kamere/rename?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,  # Use 12-digit ID
                              'camera_name': 'Hacked Camera Name'
                          },
                          content_type='application/json')
    
    # Should fail (camera not found for this user)
    assert response.status_code == 404
    data = json.loads(response.data)
    assert data['success'] is False
    
    # Verify camera name was not changed
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (int(unique_camera_id),)
        ).fetchone()
        assert camera is not None
        assert camera['camera_name'] == camera_data['camera_name']  # Should be unchanged


@pytest.mark.cameras
def test_camera_id_uniqueness_validation(client, test_users, sample_camera_data):
    """Test camera ID uniqueness validation."""
    user = test_users['regular']
    camera_data = sample_camera_data['valid']
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    # Add first camera
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,
                              'camera_name': camera_data['camera_name']
                          },
                          content_type='application/json')
    assert response.status_code == 200
    
    # Try to add camera with same ID but different name
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,
                              'camera_name': 'Different Camera Name'
                          },
                          content_type='application/json')
    
    # Should fail due to duplicate camera_id
    assert response.status_code == 409  # Conflict
    data = json.loads(response.data)
    assert data['success'] is False
    assert 'već postoji' in data['message']  # Croatian message about already exists
    
    # Verify only one camera exists with this ID
    with client.application.app_context():
        db = get_db()
        cameras = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (int(unique_camera_id),)
        ).fetchall()
        assert len(cameras) == 1  # Should be exactly 1


@pytest.mark.cameras
def test_camera_operations_require_authentication(client):
    """Test camera operations require authentication."""
    # Unauthenticated client should not access camera endpoints
    
    endpoints_to_test = [
        ('/api/kamere', 'GET'),
        ('/api/kamere/add', 'POST'),
        ('/api/kamere/rename', 'POST'),
        ('/api/kamere/delete', 'POST')
    ]
    
    for endpoint, method in endpoints_to_test:
        if method == 'GET':
            response = client.get(endpoint)
        else:
            response = client.post(endpoint, json={})
        
        # Should redirect to login or return unauthorized
        assert response.status_code in [302, 401, 403]
        
        # If it's a redirect, should go to login
        if response.status_code == 302:
            assert '/' in response.location or 'login' in response.location


@pytest.mark.cameras
def test_camera_add_with_csrf_protection(client, test_users, sample_camera_data):
    """Test camera operations work with CSRF protection."""
    user = test_users['regular']
    camera_data = sample_camera_data['valid']
    
    # Use unique camera ID
    unique_camera_id = get_unique_camera_id()
    
    # Manual login
    login_response = client.post('/login', data={
        'username': user['username'],
        'password': user['password']
    })
    assert login_response.status_code == 302
    
    # Get CSRF token
    client.get('/')
    with client.session_transaction() as sess:
        csrf_token = sess.get('csrf_token', '')
    
    # Add camera with CSRF token
    response = client.post(f'/api/kamere/add?csrf_token={csrf_token}',
                          json={
                              'camera_id': unique_camera_id,
                              'camera_name': camera_data['camera_name']
                          },
                          content_type='application/json')
    
    # Should succeed
    assert response.status_code == 200
    data = json.loads(response.data)
    assert data['success'] is True
    
    # Verify camera was added
    with client.application.app_context():
        db = get_db()
        camera = db.execute(
            'SELECT * FROM cameras WHERE camera_id = ?',
            (int(unique_camera_id),)
        ).fetchone()
        assert camera is not None