import pytest
import json
from datetime import datetime
from flask import Flask, request
from werkzeug.exceptions import NotFound

from ..core.exceptions import (
    ValidationException, InputValidationError, AuthenticationException,
    InvalidCredentialsError, DatabaseException, QueryError,
    ResourceNotFoundError, BusinessRuleViolation
)
from ..core.error_handlers import init_error_handlers, handle_errors, validate_request
from ..core.validators import Validators, validate
from ..core.monitoring import error_metrics, alert_manager
from ..middleware.error_middleware import ErrorHandlingMiddleware


@pytest.fixture
def app():
    app = Flask(__name__)
    app.config['TESTING'] = True
    app.config['SECRET_KEY'] = 'test-secret-key'
    
    init_error_handlers(app)
    ErrorHandlingMiddleware(app)
    
    @app.route('/test-validation-error')
    def test_validation_error():
        raise InputValidationError(
            field='email',
            message='Invalid email format',
            value='not-an-email'
        )
    
    @app.route('/test-auth-error')
    def test_auth_error():
        raise InvalidCredentialsError('testuser')
    
    @app.route('/test-db-error')
    def test_db_error():
        raise QueryError(
            message='Column not found',
            query='SELECT * FROM users',
            error_code='42703'
        )
    
    @app.route('/test-not-found')
    def test_not_found():
        raise ResourceNotFoundError('User', 123)
    
    @app.route('/test-generic-error')
    def test_generic_error():
        raise Exception('Something went wrong')
    
    @app.route('/test-validate', methods=['POST'])
    @validate({
        'email': {'type': 'email', 'required': True},
        'age': {'type': 'integer', 'required': True, 'min_value': 18},
        'username': {'type': 'username', 'required': False}
    })
    def test_validate():
        return {'success': True, 'data': request.validated_data}
    
    @app.route('/test-handle-errors')
    @handle_errors(
        catch=(ValueError,),
        message='Value error occurred',
        user_message='Ungültiger Wert'
    )
    def test_handle_errors():
        raise ValueError('Invalid value')
    
    return app


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


class TestExceptions:
    def test_validation_exception(self):
        exc = ValidationException(
            message='Test validation error',
            field='test_field',
            value='test_value',
            user_message='Test user message'
        )
        
        assert exc.code == 'VALIDATION_ERROR'
        assert exc.status_code == 400
        assert exc.details['field'] == 'test_field'
        assert exc.details['value'] == 'test_value'
        assert exc.user_message == 'Test user message'
    
    def test_input_validation_error(self):
        exc = InputValidationError(
            field='email',
            message='Invalid format',
            value='not-email',
            expected_type='email'
        )
        
        assert exc.code == 'VALIDATION_ERROR'
        assert exc.status_code == 400
        assert exc.details['field'] == 'email'
        assert exc.details['expected_type'] == 'email'
    
    def test_business_rule_violation(self):
        exc = BusinessRuleViolation(
            rule='max_licenses',
            message='License limit exceeded',
            context={'current': 10, 'max': 5}
        )
        
        assert exc.details['rule'] == 'max_licenses'
        assert exc.details['context']['current'] == 10
    
    def test_authentication_exceptions(self):
        exc = InvalidCredentialsError('testuser')
        assert exc.status_code == 401
        assert exc.details['username'] == 'testuser'
    
    def test_database_exceptions(self):
        exc = QueryError(
            message='Syntax error',
            query='SELECT * FORM users',
            error_code='42601'
        )
        
        assert exc.code == 'DATABASE_ERROR'
        assert exc.status_code == 500
        assert 'query_hash' in exc.details
        assert exc.details['error_code'] == '42601'
    
    def test_resource_not_found(self):
        exc = ResourceNotFoundError(
            resource_type='License',
            resource_id='ABC123',
            search_criteria={'customer_id': 1}
        )
        
        assert exc.status_code == 404
        assert exc.details['resource_type'] == 'License'
        assert exc.details['resource_id'] == 'ABC123'
        assert exc.details['search_criteria']['customer_id'] == 1


class TestErrorHandlers:
    def test_validation_error_json_response(self, client):
        response = client.get(
            '/test-validation-error',
            headers={'Accept': 'application/json'}
        )
        
        assert response.status_code == 400
        data = json.loads(response.data)
        assert 'error' in data
        assert data['error']['code'] == 'VALIDATION_ERROR'
        assert 'request_id' in data['error']
    
    def test_validation_error_html_response(self, client):
        response = client.get('/test-validation-error')
        
        assert response.status_code == 400
        assert b'Ungültiger Wert für Feld' in response.data
    
    def test_auth_error_response(self, client):
        response = client.get(
            '/test-auth-error',
            headers={'Accept': 'application/json'}
        )
        
        assert response.status_code == 401
        data = json.loads(response.data)
        assert data['error']['code'] == 'AUTHENTICATION_ERROR'
    
    def test_db_error_response(self, client):
        response = client.get(
            '/test-db-error',
            headers={'Accept': 'application/json'}
        )
        
        assert response.status_code == 500
        data = json.loads(response.data)
        assert data['error']['code'] == 'DATABASE_ERROR'
    
    def test_not_found_response(self, client):
        response = client.get('/test-not-found')
        
        assert response.status_code == 404
        assert b'User nicht gefunden' in response.data
    
    def test_generic_error_response(self, client):
        response = client.get(
            '/test-generic-error',
            headers={'Accept': 'application/json'}
        )
        
        assert response.status_code == 500
        data = json.loads(response.data)
        assert data['error']['code'] == 'INTERNAL_ERROR'
    
    def test_validate_decorator_success(self, client):
        response = client.post(
            '/test-validate',
            json={'email': 'test@example.com', 'age': 25, 'username': 'testuser'}
        )
        
        assert response.status_code == 200
        data = json.loads(response.data)
        assert data['success'] is True
        assert data['data']['email'] == 'test@example.com'
        assert data['data']['age'] == 25
    
    def test_validate_decorator_failure(self, client):
        response = client.post(
            '/test-validate',
            json={'email': 'invalid-email', 'age': 15}
        )
        
        assert response.status_code == 400
        data = json.loads(response.data)
        assert data['error']['code'] == 'VALIDATION_ERROR'
    
    def test_handle_errors_decorator(self, client):
        response = client.get('/test-handle-errors')
        
        assert response.status_code == 500
        assert b'Ungültiger Wert' in response.data


class TestValidators:
    def test_email_validator(self):
        assert Validators.email('test@example.com') == 'test@example.com'
        assert Validators.email('TEST@EXAMPLE.COM') == 'test@example.com'
        
        with pytest.raises(InputValidationError):
            Validators.email('invalid-email')
        
        with pytest.raises(InputValidationError):
            Validators.email('')
    
    def test_phone_validator(self):
        assert Validators.phone('+1-234-567-8900') == '+12345678900'
        assert Validators.phone('(123) 456-7890') == '1234567890'
        
        with pytest.raises(InputValidationError):
            Validators.phone('123')
    
    def test_license_key_validator(self):
        assert Validators.license_key('abcd-1234-efgh-5678') == 'ABCD-1234-EFGH-5678'
        
        with pytest.raises(InputValidationError):
            Validators.license_key('invalid-key')
    
    def test_integer_validator(self):
        assert Validators.integer('123') == 123
        assert Validators.integer(456) == 456
        assert Validators.integer('10', min_value=5, max_value=15) == 10
        
        with pytest.raises(InputValidationError):
            Validators.integer('abc')
        
        with pytest.raises(InputValidationError):
            Validators.integer('3', min_value=5)
        
        with pytest.raises(InputValidationError):
            Validators.integer('20', max_value=15)
    
    def test_string_validator(self):
        assert Validators.string('test', min_length=2, max_length=10) == 'test'
        
        with pytest.raises(InputValidationError):
            Validators.string('a', min_length=2)
        
        with pytest.raises(InputValidationError):
            Validators.string('a' * 20, max_length=10)
        
        with pytest.raises(InputValidationError):
            Validators.string('test<script>', safe_only=True)
    
    def test_password_validator(self):
        assert Validators.password('Test123!@#') == 'Test123!@#'
        
        with pytest.raises(InputValidationError) as exc:
            Validators.password('weak')
        assert 'at least 8 characters' in str(exc.value)
        
        with pytest.raises(InputValidationError) as exc:
            Validators.password('alllowercase123!')
        assert 'uppercase letter' in str(exc.value)
    
    def test_ip_address_validator(self):
        assert Validators.ip_address('192.168.1.1') == '192.168.1.1'
        assert Validators.ip_address('::1') == '::1'
        
        with pytest.raises(InputValidationError):
            Validators.ip_address('999.999.999.999')
        
        with pytest.raises(InputValidationError):
            Validators.ip_address('::1', version=4)
    
    def test_url_validator(self):
        assert Validators.url('http://example.com') == 'http://example.com'
        assert Validators.url('https://example.com/path') == 'https://example.com/path'
        
        with pytest.raises(InputValidationError):
            Validators.url('not-a-url')
        
        with pytest.raises(InputValidationError):
            Validators.url('http://example.com', require_https=True)
    
    def test_enum_validator(self):
        assert Validators.enum('active', 'status', ['active', 'inactive']) == 'active'
        
        with pytest.raises(InputValidationError):
            Validators.enum('pending', 'status', ['active', 'inactive'])


class TestMonitoring:
    def test_error_metrics_recording(self, client):
        initial_count = error_metrics.error_counter._value._value
        
        client.get('/test-validation-error')
        
        new_count = error_metrics.error_counter._value._value
        assert new_count > initial_count
    
    def test_alert_generation(self):
        for i in range(15):
            error = ValidationException(
                message=f'Test error {i}',
                code='TEST_ERROR'
            )
            error_metrics.record_error(error, 'test_endpoint')
        
        alerts = alert_manager.check_alerts(error_metrics)
        assert any(a['type'] == 'high_error_rate' for a in alerts)
    
    def test_metrics_endpoint(self, client):
        response = client.get('/metrics')
        
        assert response.status_code == 200
        assert b'app_errors_total' in response.data
        assert b'app_request_duration_seconds' in response.data


if __name__ == '__main__':
    pytest.main([__file__, '-v'])