use proptest::prelude::*;
use serde_json::json;
use typedialog_core::nickel::ContractValidator;

proptest! {
    #[test]
    fn test_email_validation_never_panics(input in "\\PC*") {
        let value = json!(input);
        drop(ContractValidator::validate_email(&value));
    }

    #[test]
    fn test_valid_emails_accepted(
        local in "[a-zA-Z0-9._-]{1,20}",
        domain_parts in prop::collection::vec("[a-zA-Z0-9-]{1,10}", 2..5),
        tld in "[a-z]{2,6}"
    ) {
        let domain = format!("{}.{}", domain_parts.join("."), tld);
        let email = format!("{}@{}", local, domain);
        let value = json!(email);

        let result = ContractValidator::validate_email(&value);
        prop_assert!(
            result.is_ok(),
            "Valid email format '{}' was rejected: {:?}",
            email,
            result.err()
        );
    }

    #[test]
    fn test_invalid_emails_rejected_no_at(input in "[a-zA-Z0-9._-]+") {
        let value = json!(input);
        let result = ContractValidator::validate_email(&value);
        prop_assert!(result.is_err(), "Email without @ symbol should be rejected");
    }

    #[test]
    fn test_invalid_emails_rejected_multiple_at(
        local in "[a-zA-Z0-9._-]{1,10}",
        domain in "[a-zA-Z0-9.-]{1,20}"
    ) {
        let email = format!("{}@{}@extra", local, domain);
        let value = json!(email);
        let result = ContractValidator::validate_email(&value);
        prop_assert!(result.is_err(), "Email with multiple @ symbols should be rejected");
    }

    #[test]
    fn test_invalid_emails_rejected_no_domain_dot(
        local in "[a-zA-Z0-9._-]{1,10}",
        domain in "[a-zA-Z0-9-]{1,10}"
    ) {
        let email = format!("{}@{}", local, domain);
        let value = json!(email);
        let result = ContractValidator::validate_email(&value);
        prop_assert!(
            result.is_err(),
            "Email without dot in domain '{}' should be rejected",
            email
        );
    }

    #[test]
    fn test_url_validation_never_panics(input in "\\PC*") {
        let value = json!(input);
        drop(ContractValidator::validate_url(&value));
    }

    #[test]
    fn test_valid_urls_accepted(
        scheme in prop::sample::select(&["http://", "https://", "ftp://", "ftps://"]),
        host in "[a-zA-Z0-9.-]{1,30}",
        path in prop::option::of("[a-zA-Z0-9/_-]{0,50}")
    ) {
        let url = if let Some(p) = path {
            format!("{}{}/{}", scheme, host, p)
        } else {
            format!("{}{}", scheme, host)
        };

        let value = json!(url);
        let result = ContractValidator::validate_url(&value);
        prop_assert!(
            result.is_ok(),
            "Valid URL format '{}' was rejected: {:?}",
            url,
            result.err()
        );
    }

    #[test]
    fn test_invalid_urls_rejected_no_scheme(
        host in "[a-zA-Z0-9.-]{1,20}",
        path in "[a-zA-Z0-9/_-]{0,20}"
    ) {
        let url = format!("{}/{}", host, path);
        let value = json!(url);
        let result = ContractValidator::validate_url(&value);
        prop_assert!(result.is_err(), "URL without scheme should be rejected");
    }

    #[test]
    fn test_invalid_urls_rejected_invalid_scheme(
        scheme in "[a-z]{3,8}",
        host in "[a-zA-Z0-9.-]{1,20}"
    ) {
        prop_assume!(!["http", "https", "ftp", "ftps"].contains(&scheme.as_str()));
        let url = format!("{}://{}", scheme, host);
        let value = json!(url);
        let result = ContractValidator::validate_url(&value);
        prop_assert!(
            result.is_err(),
            "URL with invalid scheme '{}' should be rejected",
            scheme
        );
    }

    #[test]
    fn test_invalid_urls_rejected_empty_host(
        scheme in prop::sample::select(&["http://", "https://", "ftp://", "ftps://"])
    ) {
        let url = scheme.to_string();
        let value = json!(url);
        let result = ContractValidator::validate_url(&value);
        prop_assert!(result.is_err(), "URL with empty host should be rejected");
    }

    #[test]
    fn test_email_validation_type_safety(value in prop_oneof![
        Just(json!(42)),
        Just(json!(true)),
        Just(json!(null)),
        Just(json!([])),
        Just(json!({})),
    ]) {
        let result = ContractValidator::validate_email(&value);
        prop_assert!(
            result.is_err(),
            "Email validation should reject non-string types"
        );
    }

    #[test]
    fn test_url_validation_type_safety(value in prop_oneof![
        Just(json!(42)),
        Just(json!(true)),
        Just(json!(null)),
        Just(json!([])),
        Just(json!({})),
    ]) {
        let result = ContractValidator::validate_url(&value);
        prop_assert!(
            result.is_err(),
            "URL validation should reject non-string types"
        );
    }

    #[test]
    fn test_empty_string_email_rejected(whitespace in "[ \\t\\n\\r]*") {
        let value = json!(whitespace);
        let result = ContractValidator::validate_email(&value);
        prop_assert!(
            result.is_err(),
            "Empty or whitespace-only email should be rejected"
        );
    }

    #[test]
    fn test_empty_string_url_rejected(whitespace in "[ \\t\\n\\r]*") {
        let value = json!(whitespace);
        let result = ContractValidator::validate_url(&value);
        prop_assert!(
            result.is_err(),
            "Empty or whitespace-only URL should be rejected"
        );
    }

    #[test]
    fn test_email_domain_boundary_dots_rejected(
        local in "[a-zA-Z0-9._-]{1,10}",
        domain in "[a-zA-Z0-9-]{1,10}",
        tld in "[a-z]{2,6}"
    ) {
        let email_start_dot = format!("{}@.{}.{}", local, domain, tld);
        let email_end_dot = format!("{}@{}.{}.", local, domain, tld);

        let value_start = json!(email_start_dot);
        let result_start = ContractValidator::validate_email(&value_start);
        prop_assert!(
            result_start.is_err(),
            "Email with domain starting with dot should be rejected"
        );

        let value_end = json!(email_end_dot);
        let result_end = ContractValidator::validate_email(&value_end);
        prop_assert!(
            result_end.is_err(),
            "Email with domain ending with dot should be rejected"
        );
    }

    #[test]
    fn test_contract_validate_integration_email(
        local in "[a-zA-Z0-9._-]{1,15}",
        domain in "[a-zA-Z0-9.-]{3,20}\\.[a-z]{2,6}"
    ) {
        let email = format!("{}@{}", local, domain);
        let value = json!(email);

        let result = ContractValidator::validate(&value, "String | std.string.Email");
        prop_assert!(
            result.is_ok() || result.is_err(),
            "Contract validation should never panic for any email input"
        );
    }

    #[test]
    fn test_contract_validate_integration_url(
        scheme in prop::sample::select(&["http", "https", "ftp", "ftps", "gopher", "file"]),
        host in "[a-zA-Z0-9.-]{1,30}"
    ) {
        let url = format!("{}://{}", scheme, host);
        let value = json!(url);

        let result = ContractValidator::validate(&value, "String | std.string.Url");
        prop_assert!(
            result.is_ok() || result.is_err(),
            "Contract validation should never panic for any URL input"
        );
    }
}

#[test]
fn test_known_valid_emails() {
    let valid_emails = vec![
        "user@example.com",
        "test.user@example.com",
        "user+tag@example.com",
        "user_name@example.co.uk",
        "a@b.c",
        "very.long.email.address@subdomain.example.org",
    ];

    for email in valid_emails {
        let value = json!(email);
        let result = ContractValidator::validate_email(&value);
        assert!(
            result.is_ok(),
            "Valid email '{}' was rejected: {:?}",
            email,
            result.err()
        );
    }
}

#[test]
fn test_known_invalid_emails() {
    let invalid_emails = vec![
        "",
        "@",
        "@@",
        "user@",
        "@example.com",
        "user",
        "user@@example.com",
        "user@example",
        "user@.example.com",
        "user@example.com.",
    ];

    for email in invalid_emails {
        let value = json!(email);
        let result = ContractValidator::validate_email(&value);
        assert!(result.is_err(), "Invalid email '{}' was accepted", email);
    }
}

#[test]
fn test_known_valid_urls() {
    let valid_urls = vec![
        "http://example.com",
        "https://example.com",
        "https://example.com/path",
        "https://subdomain.example.com/path/to/resource",
        "ftp://ftp.example.com",
        "ftps://secure.example.com",
        "http://localhost",
        "https://192.168.1.1",
    ];

    for url in valid_urls {
        let value = json!(url);
        let result = ContractValidator::validate_url(&value);
        assert!(
            result.is_ok(),
            "Valid URL '{}' was rejected: {:?}",
            url,
            result.err()
        );
    }
}

#[test]
fn test_known_invalid_urls() {
    let invalid_urls = vec![
        "",
        "http://",
        "https://",
        "example.com",
        "ftp:/example.com",
        "gopher://example.com",
        "file://example.com",
        "htp://example.com",
    ];

    for url in invalid_urls {
        let value = json!(url);
        let result = ContractValidator::validate_url(&value);
        assert!(result.is_err(), "Invalid URL '{}' was accepted", url);
    }
}
