//! Unit tests for code generation module

use super::{generate_mock_server_code, CodegenConfig, MockDataStrategy};
use crate::openapi::spec::OpenApiSpec;
use serde_json::json;

fn create_test_spec() -> OpenApiSpec {
    let spec_json = json!({
        "openapi": "3.0.0",
        "info": {
            "title": "Test API",
            "version": "1.0.0"
        },
        "paths": {
            "/users": {
                "get": {
                    "operationId": "listUsers",
                    "responses": {
                        "200": {
                            "description": "List of users",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "id": {"type": "integer"},
                                                "name": {"type": "string"}
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "post": {
                    "operationId": "createUser",
                    "requestBody": {
                        "required": true,
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "name": {"type": "string"}
                                    }
                                }
                            }
                        }
                    },
                    "responses": {
                        "201": {
                            "description": "Created",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "type": "object",
                                        "properties": {
                                            "id": {"type": "integer"},
                                            "name": {"type": "string"}
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "/users/{id}": {
                "get": {
                    "operationId": "getUserById",
                    "parameters": [
                        {
                            "name": "id",
                            "in": "path",
                            "required": true,
                            "schema": {
                                "type": "string"
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "User found",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "type": "object",
                                        "properties": {
                                            "id": {"type": "string"},
                                            "name": {"type": "string"}
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    });

    OpenApiSpec::from_json(spec_json).unwrap()
}

#[test]
fn test_generate_rust_code() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "rs", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that basic structure is present
    assert!(code.contains("pub struct GeneratedMockServer"));
    assert!(code.contains("impl GeneratedMockServer"));
    assert!(code.contains("pub fn new()"));
    assert!(code.contains("pub fn router"));
    assert!(code.contains("pub async fn start"));

    // Check that routes are generated
    // Handler names are generated from operation IDs (snake_case) or method+path
    assert!(
        code.contains("listusers") || code.contains("list_users") || code.contains("handle_get")
    );
    assert!(
        code.contains("createuser") || code.contains("create_user") || code.contains("handle_post")
    );
    assert!(
        code.contains("getuserbyid")
            || code.contains("get_user_by_id")
            || code.contains("users/:id")
    );

    // Check that Axum imports are present
    assert!(code.contains("use axum::"));
    assert!(code.contains("use serde::"));

    // Check Bradum routes are added
    assert!(code.contains(".route("));
}

#[test]
fn test_generate_rust_code_with_path_parameters() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "rs", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that path parameters are converted correctly
    // OpenAPI /users/{id} should become /users/:id in Axum
    assert!(code.contains("/users/:id") || code.contains("/users/{id}"));
}

#[test]
fn test_generate_rust_code_includes_all_methods() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "rs", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that both GET and POST are present
    assert!(code.contains(".route(\"/users\"") || code.contains("get(") || code.contains("post("));
}

#[test]
fn test_generate_rust_code_with_config() {
    let spec = create_test_spec();
    let config = CodegenConfig {
        port: Some(8080),
        enable_cors: true,
        default_delay_ms: Some(100),
        mock_data_strategy: MockDataStrategy::Examples,
    };

    let result = generate_mock_server_code(&spec, "rs", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that port is set correctly
    assert!(code.contains("8080"));

    // Check that delay is included if configured
    // Note: delay code generation is implemented, so we check for sleep
    if config.default_delay_ms.is_some() {
        assert!(code.contains("tokio::time::sleep") || code.contains("Duration::from_millis"));
    }
}

#[test]
fn test_generate_typescript_code_with_config() {
    let spec = create_test_spec();
    let config = CodegenConfig {
        port: Some(8080),
        enable_cors: true,
        default_delay_ms: Some(100),
        mock_data_strategy: MockDataStrategy::Examples,
    };

    let result = generate_mock_server_code(&spec, "ts", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that port is set correctly
    assert!(code.contains("8080"));

    // Check that CORS is enabled
    assert!(code.contains("Access-Control-Allow-Origin"));

    // Check that delay is included if configured
    if config.default_delay_ms.is_some() {
        assert!(code.contains("setTimeout") || code.contains("delay"));
    }
}

#[test]
fn test_generate_typescript_code() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "ts", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that TypeScript implementation is present
    assert!(code.contains("export class GeneratedMockServer"));
    assert!(code.contains("import express"));
    assert!(code.contains("async start()"));
    assert!(code.contains("setupRoutes"));
    assert!(code.contains("private async handle_"));
    // Check that routes are registered
    assert!(code.contains(".get(") || code.contains(".post("));
}

#[test]
fn test_unsupported_language() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "python", &config);
    assert!(result.is_err());

    let error = result.unwrap_err();
    assert!(error.to_string().contains("Unsupported language"));
}

#[test]
fn test_codegen_config_defaults() {
    let config = CodegenConfig::default();

    assert_eq!(config.mock_data_strategy, MockDataStrategy::ExamplesOrRandom);
    assert_eq!(config.port, None);
    assert!(!config.enable_cors);
    assert_eq!(config.default_delay_ms, None);
}

#[test]
fn test_generate_handler_signatures() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "rs", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that async handler functions are generated
    assert!(code.contains("async fn handle_"));

    // Check that handlers return Json<Value>
    assert!(code.contains("Json<Value>") || code.contains("Json<"));

    // Check that POST handler has request body parameter
    assert!(code.contains("Json(body)") || code.contains("body"));
}

#[test]
fn test_main_function_generated() {
    let spec = create_test_spec();
    let config = CodegenConfig::default();

    let result = generate_mock_server_code(&spec, "rs", &config);
    assert!(result.is_ok());

    let code = result.unwrap();

    // Check that main function is present
    assert!(code.contains("#[tokio::main]"));
    assert!(code.contains("async fn main"));
    assert!(code.contains("GeneratedMockServer::new()"));
    assert!(code.contains("server.start()"));
}
