use proxywarden_lib::models::{
    ComponentId, ProfileInput, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol,
    TargetInput, TargetKind,
};
use proxywarden_lib::validation::{normalize_profile, normalize_target};

#[test]
fn normalizes_profile_source_items() {
    let profile = normalize_profile(ProfileInput {
        id: Some("Discord + Vesktop".to_string()),
        name: " Discord + Vesktop ".to_string(),
        enabled: true,
        target_id: " home-gateway ".to_string(),
        protocols: vec!["tcp".to_string(), "UDP".to_string(), "TCP".to_string()],
        items: vec![
            ProfileItemInput {
                item_type: "process".to_string(),
                value: "Discord.exe".to_string(),
                recursive: None,
            },
            ProfileItemInput {
                item_type: "folder".to_string(),
                value: "%LOCALAPPDATA%\\Vesktop".to_string(),
                recursive: Some(true),
            },
            ProfileItemInput {
                item_type: "exe".to_string(),
                value: "C:\\Games\\Game\\game.exe".to_string(),
                recursive: Some(true),
            },
        ],
    })
    .expect("profile should normalize");

    assert_eq!(profile.id, "discord-vesktop");
    assert_eq!(profile.name, "Discord + Vesktop");
    assert_eq!(profile.target_id, "home-gateway");
    assert_eq!(profile.protocols, vec![Protocol::Tcp, Protocol::Udp]);
    assert_eq!(profile.items[0].item_type, ProfileItemType::Process);
    assert_eq!(profile.items[0].value, "Discord");
    assert!(!profile.items[0].recursive);
    assert_eq!(profile.items[1].item_type, ProfileItemType::Folder);
    assert!(profile.items[1].recursive);
    assert_eq!(profile.items[2].item_type, ProfileItemType::Exe);
    assert!(!profile.items[2].recursive);
}

#[test]
fn rejects_unsupported_profile_protocols() {
    let error = normalize_profile(ProfileInput {
        id: None,
        name: "Bad protocol".to_string(),
        enabled: true,
        target_id: "home-gateway".to_string(),
        protocols: vec!["icmp".to_string()],
        items: vec![ProfileItemInput {
            item_type: "process".to_string(),
            value: "Discord".to_string(),
            recursive: None,
        }],
    })
    .expect_err("unsupported protocol should fail");

    assert!(error.iter().any(|item| item.field == "protocols"));
}

#[test]
fn normalizes_external_target_without_local_singbox() {
    let target = normalize_target(TargetInput {
        id: Some("Home Gateway".to_string()),
        name: " Home Gateway ".to_string(),
        kind: "external".to_string(),
        protocol: "socks5".to_string(),
        host: " 192.168.50.111 ".to_string(),
        port: 8080,
        requires_component: None,
    })
    .expect("external target should normalize");

    assert_eq!(target.id, "home-gateway");
    assert_eq!(target.kind, TargetKind::External);
    assert_eq!(target.protocol, ProxyProtocol::Socks5);
    assert_eq!(target.host, "192.168.50.111");
    assert_eq!(target.port, 8080);
    assert_eq!(target.requires_component, None);
}

#[test]
fn local_singbox_target_can_exist_before_component_is_installed() {
    let target = normalize_target(TargetInput {
        id: Some("local-singbox".to_string()),
        name: "Local sing-box".to_string(),
        kind: "local".to_string(),
        protocol: "socks5".to_string(),
        host: "127.0.0.1".to_string(),
        port: 1080,
        requires_component: Some("singbox".to_string()),
    })
    .expect("local target definition should not require installed component");

    assert_eq!(target.kind, TargetKind::Local);
    assert_eq!(target.requires_component, Some(ComponentId::Singbox));
}

#[test]
fn rejects_malformed_target_fields() {
    let error = normalize_target(TargetInput {
        id: None,
        name: "".to_string(),
        kind: "external".to_string(),
        protocol: "ftp".to_string(),
        host: "".to_string(),
        port: 70_000,
        requires_component: Some("unknown".to_string()),
    })
    .expect_err("invalid target should fail");

    assert!(error.iter().any(|item| item.field == "name"));
    assert!(error.iter().any(|item| item.field == "host"));
    assert!(error.iter().any(|item| item.field == "port"));
    assert!(error.iter().any(|item| item.field == "protocol"));
    assert!(error.iter().any(|item| item.field == "requires_component"));
}
