//! Common tests for Veza Common Library
//! 
//! This module provides integration tests and test utilities for the Veza common library.

use veza_common::utils::{
    generate_uuid, format_duration, format_file_size,
    validate_email, validate_username, validate_password,
    validate_email_result, validate_username_result,
    to_json, from_json,
    format_timestamp, parse_date, format_relative_time,
    format_log_message, StructuredLogEntry,
};
use veza_common::types::{
    Track,
};
use veza_common::config::{
    DatabaseConfig, RedisConfig,
};
use veza_common::error::{
    CommonError, ErrorResponse,
};

/// Test fixtures for common library tests
pub mod fixtures {
    use veza_common::types::{User, Track, Playlist};
    use veza_common::config::{DatabaseConfig, RedisConfig};
    use uuid::Uuid;
    use std::collections::HashMap;

    /// Create a test user
    pub fn create_test_user(id: i64) -> User {
        User::new(id, format!("testuser{}", id), format!("test{}@example.com", id))
    }

    /// Create a test track
    pub fn create_test_track(id: Uuid) -> Track {
        Track::new(id, "Test Track".to_string(), "Test Artist".to_string(), 180)
    }

    /// Create a test playlist
    pub fn create_test_playlist(id: Uuid, owner_id: Uuid) -> Playlist {
        Playlist::new(id, "Test Playlist".to_string(), owner_id)
    }

    /// Create a test database configuration
    pub fn create_test_database_config() -> DatabaseConfig {
        DatabaseConfig::new(
            "postgresql://test:test@localhost:5432/test_db".to_string(),
            10
        )
    }

    /// Create a test Redis configuration
    pub fn create_test_redis_config() -> RedisConfig {
        RedisConfig::new("redis://localhost:6379".to_string())
    }

    /// Create test context for logging
    pub fn create_test_context() -> HashMap<String, String> {
        let mut context = HashMap::new();
        context.insert("user_id".to_string(), "123".to_string());
        context.insert("ip".to_string(), "192.168.1.1".to_string());
        context.insert("request_id".to_string(), Uuid::new_v4().to_string());
        context
    }
}

/// Test helpers for common library tests
pub mod helpers {
    use veza_common::types::{ApiResponse, PaginationParams, PaginatedResponse};

    /// Assert that a result is ok
    pub fn assert_ok<T, E: std::fmt::Debug>(result: Result<T, E>) -> T {
        result.expect("Expected Ok result")
    }

    /// Assert that a result is an error
    pub fn assert_err<T: std::fmt::Debug, E>(result: Result<T, E>) -> E {
        result.expect_err("Expected Err result")
    }

    /// Assert that two values are equal
    pub fn assert_eq_expected<T: PartialEq + std::fmt::Debug>(
        actual: T,
        expected: T,
        message: &str,
    ) {
        assert_eq!(
            actual, expected,
            "{}: expected {:?}, got {:?}",
            message, expected, actual
        );
    }

    /// Create a test API response
    pub fn create_test_api_response<T>(data: T) -> ApiResponse<T> {
        ApiResponse::success(data)
    }

    /// Create a test error API response
    pub fn create_test_error_response(message: &str) -> ApiResponse<String> {
        ApiResponse::error(message.to_string())
    }

    /// Create test pagination parameters
    pub fn create_test_pagination(page: u32, limit: u32) -> PaginationParams {
        PaginationParams { page, limit }
    }

    /// Create test paginated response
    pub fn create_test_paginated_response<T>(items: Vec<T>, total: u64) -> PaginatedResponse<T> {
        PaginatedResponse::new(items, total, 1, 20)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use fixtures::*;
    use helpers::*;
    use uuid::Uuid;

    #[test]
    fn test_common_utilities() {
        // Test UUID generation
        let uuid1 = generate_uuid();
        let uuid2 = generate_uuid();
        assert_ne!(uuid1, uuid2);

        // Test duration formatting
        let formatted = format_duration(125);
        assert_eq!(formatted, "2:05");

        // Test file size formatting
        let size = format_file_size(1024);
        assert_eq!(size, "1.00 KB");
    }

    #[test]
    fn test_validation_utilities() {
        // Test email validation
        assert!(validate_email("test@example.com"));
        assert!(!validate_email("invalid-email"));

        // Test username validation
        assert!(validate_username("user123"));
        assert!(!validate_username("ab")); // Too short

        // Test password validation
        assert!(validate_password("Password123"));
        assert!(!validate_password("short")); // Too short
    }

    #[test]
    fn test_serialization_utilities() {
        use serde::{Deserialize, Serialize};

        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct TestData {
            name: String,
            value: i32,
        }

        let data = TestData {
            name: "test".to_string(),
            value: 42,
        };

        // Test serialization
        let json = to_json(&data).unwrap();
        assert!(json.contains("test"));
        assert!(json.contains("42"));

        // Test deserialization
        let deserialized: TestData = from_json(&json).unwrap();
        assert_eq!(data, deserialized);
    }

    #[test]
    fn test_date_utilities() {
        use chrono::{Utc, Duration, Datelike};

        // Test timestamp formatting
        let timestamp = 1609459200; // 2021-01-01 00:00:00 UTC
        let formatted = format_timestamp(timestamp);
        assert!(formatted.contains("2021"));

        // Test date parsing
        let date_str = "2021-01-01T00:00:00Z";
        let dt = parse_date(date_str).unwrap();
        assert_eq!(dt.year(), 2021);

        // Test relative time
        let now = Utc::now();
        let past = now - Duration::hours(2);
        let relative = format_relative_time(&past, &now);
        assert!(relative.contains("2"));
        assert!(relative.contains("hour"));
    }

    #[test]
    fn test_logging_utilities() {
        // Test log message formatting
        let message = format_log_message("api", "INFO", "Test message");
        assert!(message.contains("api"));
        assert!(message.contains("INFO"));
        assert!(message.contains("Test message"));

        // Test structured log entry
        let entry = StructuredLogEntry::new("api", "INFO", "Test message")
            .with_context("user_id".to_string(), "123".to_string());
        
        let json = entry.to_json();
        assert!(json.contains("api"));
        assert!(json.contains("user_id"));
        assert!(json.contains("123"));
    }

    #[test]
    fn test_type_utilities() {
        // Test User
        let user = create_test_user(1);
        assert_eq!(user.id, 1);
        assert_eq!(user.username, "testuser1");
        assert!(user.validate().is_ok());

        // Test Track
        let track_id = Uuid::new_v4();
        let track = create_test_track(track_id);
        assert_eq!(track.id, track_id);
        assert_eq!(track.title, "Test Track");
        assert!(track.validate().is_ok());

        // Test Playlist
        let playlist_id = Uuid::new_v4();
        let owner_id = Uuid::new_v4();
        let playlist = create_test_playlist(playlist_id, owner_id);
        assert_eq!(playlist.id, playlist_id);
        assert_eq!(playlist.owner_id, owner_id);
        assert!(playlist.validate().is_ok());
    }

    #[test]
    fn test_config_utilities() {
        // Test DatabaseConfig
        let db_config = create_test_database_config();
        assert!(db_config.validate().is_ok());
        assert_eq!(db_config.host(), Some("localhost".to_string()));
        assert_eq!(db_config.port(), Some(5432));

        // Test RedisConfig
        let redis_config = create_test_redis_config();
        assert!(redis_config.validate().is_ok());
        assert_eq!(redis_config.host(), Some("localhost".to_string()));
        assert_eq!(redis_config.port(), Some(6379));
    }

    #[test]
    fn test_api_response_utilities() {
        // Test success response
        let response = create_test_api_response("test data");
        assert!(response.success);
        assert!(response.data.is_some());
        assert_eq!(response.data.unwrap(), "test data");

        // Test error response
        let error_response = create_test_error_response("Test error");
        assert!(!error_response.success);
        assert!(error_response.data.is_none());
        assert_eq!(error_response.error, Some("Test error".to_string()));
    }

    #[test]
    fn test_pagination_utilities() {
        // Test pagination parameters
        let params = create_test_pagination(2, 50);
        assert_eq!(params.page, 2);
        assert_eq!(params.limit, 50);

        // Test paginated response
        let items = vec![1, 2, 3, 4, 5];
        let response = create_test_paginated_response(items.clone(), 100);
        assert_eq!(response.items.len(), 5);
        assert_eq!(response.total, 100);
        assert_eq!(response.total_pages, 5); // 100 / 20
    }

    #[test]
    fn test_error_handling() {
        // Test CommonError
        let error = CommonError::NotFound("Resource not found".to_string());
        assert_eq!(error.code(), "NOT_FOUND");
        assert_eq!(error.http_status_code(), 404);
        assert_eq!(error.message(), "Resource not found");

        // Test ErrorResponse
        let error_response: ErrorResponse = (&error).into();
        assert_eq!(error_response.code, "NOT_FOUND");
        assert_eq!(error_response.status, 404);
    }

    #[test]
    fn test_validation_result() {
        // Test validation with Result
        assert!(validate_email_result("test@example.com").is_ok());
        assert!(validate_email_result("invalid-email").is_err());

        assert!(validate_username_result("user123").is_ok());
        assert!(validate_username_result("ab").is_err());
    }

    #[test]
    fn test_config_validation() {
        // Test DatabaseConfig validation
        let mut db_config = create_test_database_config();
        assert!(db_config.validate().is_ok());

        // Test invalid URL
        db_config.url = "invalid-url".to_string();
        assert!(db_config.validate().is_err());

        // Test RedisConfig validation
        let mut redis_config = create_test_redis_config();
        assert!(redis_config.validate().is_ok());

        // Test invalid URL
        redis_config.url = "invalid-url".to_string();
        assert!(redis_config.validate().is_err());
    }

    #[test]
    fn test_playlist_operations() {
        let playlist_id = Uuid::new_v4();
        let owner_id = Uuid::new_v4();
        let track_id = Uuid::new_v4();
        
        let mut playlist = create_test_playlist(playlist_id, owner_id);
        
        // Test adding tracks
        playlist.add_track(track_id);
        assert_eq!(playlist.track_count(), 1);
        assert!(!playlist.is_empty());

        // Test removing tracks
        playlist.remove_track(track_id);
        assert_eq!(playlist.track_count(), 0);
        assert!(playlist.is_empty());
    }

    #[test]
    fn test_track_utilities() {
        let track_id = Uuid::new_v4();
        let track = Track::new(track_id, "Test Song".to_string(), "Test Artist".to_string(), 125);
        
        // Test formatted duration
        assert_eq!(track.formatted_duration(), "2:05");
        
        // Test validation
        assert!(track.validate().is_ok());
    }

    #[test]
    fn test_helper_functions() {
        // Test assert_ok
        let ok_result: Result<i32, String> = Ok(42);
        let value = assert_ok(ok_result);
        assert_eq!(value, 42);

        // Test assert_err
        let err_result: Result<i32, String> = Err("Error".to_string());
        let error = assert_err(err_result);
        assert_eq!(error, "Error");

        // Test assert_eq_expected
        assert_eq_expected(5, 5, "Values should be equal");
    }

    #[test]
    fn test_context_creation() {
        let context = create_test_context();
        assert!(context.contains_key("user_id"));
        assert!(context.contains_key("ip"));
        assert!(context.contains_key("request_id"));
    }

    #[test]
    fn test_round_trip_serialization() {
        use serde::{Deserialize, Serialize};

        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct TestStruct {
            id: i32,
            name: String,
            active: bool,
        }

        let original = TestStruct {
            id: 1,
            name: "Test".to_string(),
            active: true,
        };

        // Serialize
        let json = to_json(&original).unwrap();
        
        // Deserialize
        let deserialized: TestStruct = from_json(&json).unwrap();
        
        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_config_url_parsing() {
        // Test database URL parsing
        let db_config = DatabaseConfig::new(
            "postgresql://user:pass@localhost:5433/mydb?sslmode=require".to_string(),
            10
        );
        assert_eq!(db_config.host(), Some("localhost".to_string()));
        assert_eq!(db_config.port(), Some(5433));
        assert_eq!(db_config.database_name(), Some("mydb".to_string()));

        // Test Redis URL parsing
        let redis_config = RedisConfig::new("redis://user:pass@localhost:6380/1".to_string());
        assert_eq!(redis_config.host(), Some("localhost".to_string()));
        assert_eq!(redis_config.port(), Some(6380));
    }
}

