// Error Handling Demo Example
// Run with: cargo run --example error_handling_demo --features actix

#![cfg(feature = "actix")]

use actix_web::{App, HttpServer, get, post, web};
use anycms_core::{ApiResult, ResultPagination};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct CreateUserRequest {
    name: String,
    email: String,
}

// ============================================================
// Custom Error Type (plain enum, no thiserror needed)
// ============================================================

#[derive(Debug)]
enum UserServiceError {
    UserNotFound(u32),
    EmailAlreadyExists(String),
    InvalidEmail(String),
    Database(String),
}

impl std::fmt::Display for UserServiceError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            UserServiceError::UserNotFound(id) => write!(f, "User with id {} not found", id),
            UserServiceError::EmailAlreadyExists(email) => write!(f, "Email {} already exists", email),
            UserServiceError::InvalidEmail(email) => write!(f, "Invalid email format: {}", email),
            UserServiceError::Database(msg) => write!(f, "Database error: {}", msg),
        }
    }
}

/// Convert UserServiceError into ApiResult with the appropriate HTTP status code
impl From<UserServiceError> for ApiResult<User> {
    fn from(err: UserServiceError) -> Self {
        match err {
            UserServiceError::UserNotFound(id) => {
                ApiResult::fail(&format!("User with id {} not found", id)).with_code(404)
            }
            UserServiceError::EmailAlreadyExists(email) => {
                ApiResult::fail(&format!("Email {} already registered", email)).with_code(409)
            }
            UserServiceError::InvalidEmail(email) => {
                ApiResult::fail(&format!("Invalid email format: {}", email)).with_code(422)
            }
            UserServiceError::Database(msg) => {
                ApiResult::fail(&format!("Database error: {}", msg)).with_code(500)
            }
        }
    }
}

// ============================================================
// Mock Data & Functions
// ============================================================

async fn get_users_mock() -> Vec<User> {
    vec![
        User {
            id: 1,
            name: "张三".to_string(),
            email: "zhangsan@example.com".to_string(),
        },
        User {
            id: 2,
            name: "李四".to_string(),
            email: "lisi@example.com".to_string(),
        },
    ]
}

async fn find_user_by_id(id: u32) -> Result<User, UserServiceError> {
    let users = get_users_mock().await;
    users
        .into_iter()
        .find(|u| u.id == id)
        .ok_or(UserServiceError::UserNotFound(id))
}

async fn validate_email(email: &str) -> Result<(), UserServiceError> {
    if email.contains('@') && email.contains('.') {
        Ok(())
    } else {
        Err(UserServiceError::InvalidEmail(email.to_string()))
    }
}

// ============================================================
// API Handlers - Different Error Handling Patterns
// ============================================================

/// Pattern 1: Using Into<ApiResult> conversion
#[get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> ApiResult<User> {
    let user_id = path.into_inner();

    find_user_by_id(user_id)
        .await
        .map(|user| ApiResult::value(user))
        .unwrap_or_else(|e| e.into())
}

/// Pattern 2: Using match with direct ApiResult construction
#[get("/users2/{id}")]
async fn get_user_v2(path: web::Path<u32>) -> ApiResult<User> {
    let user_id = path.into_inner();

    match find_user_by_id(user_id).await {
        Ok(user) => ApiResult::value(user),
        Err(_) => ApiResult::fail(&format!("User {} not found", user_id)).with_code(404),
    }
}

/// Pattern 3: Using ApiResult::from_result for generic error mapping
#[get("/users3/{id}")]
async fn get_user_v3(path: web::Path<u32>) -> ApiResult<User> {
    let user_id = path.into_inner();

    find_user_by_id(user_id)
        .await
        .map(|user| ApiResult::value(user))
        .unwrap_or_else(|e| {
            ApiResult::fail(&e.to_string()).with_code(404)
        })
}

/// Pattern 4: Manual error handling with early returns
#[post("/users")]
async fn create_user(user_data: web::Json<CreateUserRequest>) -> ApiResult<User> {
    // Validate email
    if let Err(e) = validate_email(&user_data.email).await {
        return e.into();
    }

    // Check if email already exists (mock)
    if user_data.email == "existing@example.com" {
        return ApiResult::fail("Email already registered").with_code(409);
    }

    // Create user (mock)
    let new_user = User {
        id: 999,
        name: user_data.name.clone(),
        email: user_data.email.clone(),
    };

    ApiResult::value(new_user).with_message("User created successfully")
}

/// Pattern 5: Using different error codes based on context
#[get("/users/search")]
async fn search_users(
    query: web::Query<std::collections::HashMap<String, String>>,
) -> ApiResult<User> {
    let email = query.get("email");

    if let Some(email) = email {
        if email.is_empty() {
            return ApiResult::fail("Email parameter cannot be empty").with_code(400);
        }

        if !email.contains('@') {
            return ApiResult::fail("Invalid email format").with_code(422);
        }
    }

    let users = get_users_mock().await;
    ApiResult::list(users).with_pagination(ResultPagination::new(100, 1, 10))
}

/// Pattern 6: Empty result with just status
#[get("/health")]
async fn health_check() -> ApiResult<()> {
    ApiResult::ok()
}

/// Pattern 7: Using anyhow for unexpected errors
#[get("/users/{id}/profile")]
async fn get_user_profile(path: web::Path<u32>) -> ApiResult<serde_json::Value> {
    let user_id = path.into_inner();

    let user = match find_user_by_id(user_id).await {
        Ok(u) => u,
        Err(e) => return ApiResult::fail(&e.to_string()).with_code(404),
    };

    // Simulate some processing that might fail
    let profile = serde_json::json!({
        "user": user,
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-15T10:30:00Z",
    });

    ApiResult::value(profile)
}

/// Pattern 8: Demonstrating HTTP status code mapping
#[get("/error-demo/{type}")]
async fn error_demo(path: web::Path<String>) -> ApiResult<()> {
    let error_type = path.into_inner();

    match error_type.as_str() {
        "not_found" => ApiResult::fail("Resource not found").with_code(404),
        "bad_request" => ApiResult::fail("Invalid parameters").with_code(400),
        "unauthorized" => ApiResult::fail("Please login first").with_code(401),
        "forbidden" => ApiResult::fail("You don't have permission").with_code(403),
        "validation" => ApiResult::fail("Validation failed").with_code(422),
        "conflict" => ApiResult::fail("Resource already exists").with_code(409),
        "internal" => ApiResult::fail("Something went wrong").with_code(500),
        _ => ApiResult::ok(),
    }
}

// ============================================================
// Main
// ============================================================

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    println!("Starting Error Handling Demo Server...");
    println!("\nAvailable endpoints:");
    println!("\n  Error Handling Patterns:");
    println!("  GET  /users/{{id}}           - Pattern 1: Using From conversion");
    println!("  GET  /users2/{{id}}          - Pattern 2: Match with direct construction");
    println!("  GET  /users3/{{id}}          - Pattern 3: from_result / unwrap_or_else");
    println!("  POST /users                  - Pattern 4: Manual error handling");
    println!("  GET  /users/search           - Pattern 5: Context-based errors");
    println!("  GET  /health                 - Pattern 6: Empty result");
    println!("  GET  /users/{{id}}/profile   - Pattern 7: Complex response");
    println!("\n  Error Demo (HTTP Status Code Mapping):");
    println!("  GET  /error-demo/not_found      - Returns 404");
    println!("  GET  /error-demo/bad_request    - Returns 400");
    println!("  GET  /error-demo/unauthorized   - Returns 401");
    println!("  GET  /error-demo/forbidden      - Returns 403");
    println!("  GET  /error-demo/validation     - Returns 422");
    println!("  GET  /error-demo/conflict       - Returns 409");
    println!("  GET  /error-demo/internal       - Returns 500");
    println!("\nTry these examples:");
    println!("  curl http://localhost:8080/users/1        # Success");
    println!("  curl http://localhost:8080/users/999       # 404 Not Found");
    println!("  curl http://localhost:8080/error-demo/not_found  # See HTTP status code");

    HttpServer::new(|| {
        App::new()
            .service(get_user)
            .service(get_user_v2)
            .service(get_user_v3)
            .service(create_user)
            .service(search_users)
            .service(health_check)
            .service(get_user_profile)
            .service(error_demo)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await?;

    Ok(())
}
