mod common;

use common::run_lua;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[tokio::test]
async fn test_http_success() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/health"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.http("{}/health")
        assert.eq(result.ok, true)
        assert.eq(result.status, 200)
        assert.eq(result.error, nil)
        assert.not_nil(result.latency_ms)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_http_failure() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/health"))
        .respond_with(ResponseTemplate::new(500))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.http("{}/health")
        assert.eq(result.ok, false)
        assert.eq(result.status, 500)
        assert.not_nil(result.error)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_http_custom_expected_status() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/created"))
        .respond_with(ResponseTemplate::new(201))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.http("{}/created", {{ expected_status = 201 }})
        assert.eq(result.ok, true)
        assert.eq(result.status, 201)
        assert.eq(result.error, nil)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_json_path_matching() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "status": {
                "ready": true,
                "version": "1.2.3"
            }
        })))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.json_path("{}/api/status", "status.ready", true)
        assert.eq(result.ok, true)
        assert.eq(result.actual, true)
        assert.eq(result.expected, true)
        assert.eq(result.error, nil)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_json_path_non_matching() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "status": {
                "ready": false
            }
        })))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.json_path("{}/api/status", "status.ready", true)
        assert.eq(result.ok, false)
        assert.eq(result.actual, false)
        assert.eq(result.expected, true)
        assert.not_nil(result.error)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_json_path_missing() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"status": {}})))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.json_path("{}/api/status", "status.ready", true)
        assert.eq(result.ok, false)
        assert.eq(result.actual, nil)
        assert.not_nil(result.error)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_status_code_match() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/ping"))
        .respond_with(ResponseTemplate::new(204))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.status_code("{}/ping", 204)
        assert.eq(result.ok, true)
        assert.eq(result.status, 204)
        assert.eq(result.error, nil)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_status_code_mismatch() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/ping"))
        .respond_with(ResponseTemplate::new(503))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.status_code("{}/ping", 200)
        assert.eq(result.ok, false)
        assert.eq(result.status, 503)
        assert.not_nil(result.error)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_body_contains_found() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/info"))
        .respond_with(ResponseTemplate::new(200).set_body_string("server is healthy and running"))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.body_contains("{}/info", "healthy")
        assert.eq(result.ok, true)
        assert.eq(result.found, true)
        assert.eq(result.error, nil)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_body_contains_not_found() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/info"))
        .respond_with(ResponseTemplate::new(200).set_body_string("server is down"))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.body_contains("{}/info", "healthy")
        assert.eq(result.ok, false)
        assert.eq(result.found, false)
        assert.not_nil(result.error)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_endpoint_healthy() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/ready"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.endpoint("{}/ready")
        assert.eq(result.ok, true)
        assert.eq(result.status, 200)
        assert.not_nil(result.latency_ms)
        assert.eq(result.error, nil)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_endpoint_unhealthy_status() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/ready"))
        .respond_with(ResponseTemplate::new(503))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.endpoint("{}/ready")
        assert.eq(result.ok, false)
        assert.eq(result.status, 503)
        assert.not_nil(result.error)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_multi_all_pass() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/svc1/health"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&server)
        .await;
    Mock::given(method("GET"))
        .and(path("/svc2/health"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local base = "{}"
        local result = hc.multi({{{{
            name = "service-1",
            check = function() return hc.http(base .. "/svc1/health") end,
        }}, {{
            name = "service-2",
            check = function() return hc.http(base .. "/svc2/health") end,
        }}}})
        assert.eq(result.ok, true)
        assert.eq(result.passed, 2)
        assert.eq(result.failed, 0)
        assert.eq(result.total, 2)
        assert.eq(result.results[1].name, "service-1")
        assert.eq(result.results[1].ok, true)
        assert.eq(result.results[2].name, "service-2")
        assert.eq(result.results[2].ok, true)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_multi_some_fail() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/svc1/health"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&server)
        .await;
    Mock::given(method("GET"))
        .and(path("/svc2/health"))
        .respond_with(ResponseTemplate::new(503))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local base = "{}"
        local result = hc.multi({{{{
            name = "service-1",
            check = function() return hc.http(base .. "/svc1/health") end,
        }}, {{
            name = "service-2",
            check = function() return hc.http(base .. "/svc2/health") end,
        }}}})
        assert.eq(result.ok, false)
        assert.eq(result.passed, 1)
        assert.eq(result.failed, 1)
        assert.eq(result.total, 2)
        assert.eq(result.results[1].ok, true)
        assert.eq(result.results[2].ok, false)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_healthcheck_wait_success() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/health"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        local result = hc.wait("{}/health", {{ timeout = 5, interval = 0.1 }})
        assert.eq(result.ok, true)
        assert.eq(result.attempts, 1)
        "#,
        server.uri()
    );
    run_lua(&script).await.unwrap();
}

#[tokio::test]
async fn test_healthcheck_wait_timeout() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/health"))
        .respond_with(ResponseTemplate::new(503))
        .mount(&server)
        .await;

    let script = format!(
        r#"
        local hc = require("assay.healthcheck")
        hc.wait("{}/health", {{ timeout = 1, interval = 0.5 }})
        "#,
        server.uri()
    );
    let result = run_lua(&script).await;
    assert!(result.is_err());
}
