use crate::models::{
    ComponentId, Profile, ProfileInput, ProfileItem, ProfileItemType, Protocol, ProxyProtocol,
    Target, TargetInput, TargetKind,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationError {
    pub field: String,
    pub message: String,
}

pub type ValidationResult<T> = Result<T, Vec<ValidationError>>;

fn error(field: impl Into<String>, message: impl Into<String>) -> ValidationError {
    ValidationError {
        field: field.into(),
        message: message.into(),
    }
}

fn clean(value: &str) -> String {
    value.trim().to_string()
}

fn slug(value: &str, fallback: &str) -> String {
    let mut output = String::new();
    let mut previous_dash = false;

    for ch in value.trim().to_lowercase().chars() {
        if ch.is_ascii_alphanumeric() {
            output.push(ch);
            previous_dash = false;
        } else if !previous_dash {
            output.push('-');
            previous_dash = true;
        }
    }

    let output = output.trim_matches('-').to_string();
    if output.is_empty() {
        fallback.to_string()
    } else {
        output
    }
}

fn process_name(value: &str) -> String {
    let base = value.trim().rsplit(['\\', '/']).next().unwrap_or("").trim();
    base.strip_suffix(".exe")
        .or_else(|| base.strip_suffix(".EXE"))
        .unwrap_or(base)
        .trim()
        .to_string()
}

pub fn parse_protocol(value: &str) -> Result<Protocol, ValidationError> {
    match value.trim().to_ascii_uppercase().as_str() {
        "TCP" => Ok(Protocol::Tcp),
        "UDP" => Ok(Protocol::Udp),
        _ => Err(error(
            "protocols",
            format!("Неподдерживаемый протокол: {value}"),
        )),
    }
}

pub fn parse_profile_item_type(value: &str) -> Result<ProfileItemType, ValidationError> {
    match value.trim().to_ascii_lowercase().as_str() {
        "process" => Ok(ProfileItemType::Process),
        "folder" => Ok(ProfileItemType::Folder),
        "exe" => Ok(ProfileItemType::Exe),
        _ => Err(error(
            "items.type",
            format!("Неподдерживаемый тип элемента: {value}"),
        )),
    }
}

pub fn parse_target_kind(value: &str) -> Result<TargetKind, ValidationError> {
    match value.trim().to_ascii_lowercase().as_str() {
        "local" => Ok(TargetKind::Local),
        "external" => Ok(TargetKind::External),
        _ => Err(error("kind", format!("Неподдерживаемый тип цели: {value}"))),
    }
}

pub fn parse_proxy_protocol(value: &str) -> Result<ProxyProtocol, ValidationError> {
    match value.trim().to_ascii_lowercase().as_str() {
        "socks5" => Ok(ProxyProtocol::Socks5),
        "http" => Ok(ProxyProtocol::Http),
        _ => Err(error(
            "protocol",
            format!("Неподдерживаемый протокол прокси: {value}"),
        )),
    }
}

pub fn parse_component_id(value: &str) -> Result<ComponentId, ValidationError> {
    match value.trim().to_ascii_lowercase().as_str() {
        "control-app" | "controlapp" => Ok(ComponentId::ControlApp),
        "proxyfier" => Ok(ComponentId::Proxyfier),
        "singbox" | "sing-box" => Ok(ComponentId::Singbox),
        _ => Err(error(
            "requires_component",
            format!("Неподдерживаемый компонент: {value}"),
        )),
    }
}

pub fn normalize_profile(input: ProfileInput) -> ValidationResult<Profile> {
    let mut errors = Vec::new();
    let name = clean(&input.name);
    if name.is_empty() {
        errors.push(error("name", "Укажите название профиля"));
    }

    let target_id = clean(&input.target_id);
    if target_id.is_empty() {
        errors.push(error("target_id", "Укажите цель профиля"));
    }

    let mut protocols = Vec::new();
    for value in input.protocols {
        match parse_protocol(&value) {
            Ok(protocol) if !protocols.contains(&protocol) => protocols.push(protocol),
            Ok(_) => {}
            Err(err) => errors.push(err),
        }
    }
    if protocols.is_empty() {
        errors.push(error("protocols", "Выберите хотя бы один протокол"));
    }

    let mut items = Vec::new();
    for raw_item in input.items {
        let item_type = match parse_profile_item_type(&raw_item.item_type) {
            Ok(item_type) => item_type,
            Err(err) => {
                errors.push(err);
                continue;
            }
        };

        let value = match item_type {
            ProfileItemType::Process => process_name(&raw_item.value),
            ProfileItemType::Folder | ProfileItemType::Exe => clean(&raw_item.value),
        };
        if value.is_empty() {
            errors.push(error("items.value", "Укажите значение элемента профиля"));
            continue;
        }

        let recursive =
            matches!(item_type, ProfileItemType::Folder) && raw_item.recursive.unwrap_or(true);
        items.push(ProfileItem {
            item_type,
            value,
            recursive,
        });
    }

    if !errors.is_empty() {
        return Err(errors);
    }

    Ok(Profile {
        id: slug(input.id.as_deref().unwrap_or(&name), "profile"),
        name,
        enabled: input.enabled,
        target_id,
        protocols,
        items,
    })
}

pub fn normalize_target(input: TargetInput) -> ValidationResult<Target> {
    let mut errors = Vec::new();
    let name = clean(&input.name);
    let host = clean(&input.host);

    if name.is_empty() {
        errors.push(error("name", "Укажите название цели"));
    }
    if host.is_empty() {
        errors.push(error("host", "Укажите хост цели"));
    }
    if input.port == 0 || input.port > u16::MAX as u32 {
        errors.push(error("port", "Порт цели должен быть от 1 до 65535"));
    }

    let kind = parse_target_kind(&input.kind).unwrap_or_else(|err| {
        errors.push(err);
        TargetKind::External
    });
    let protocol = parse_proxy_protocol(&input.protocol).unwrap_or_else(|err| {
        errors.push(err);
        ProxyProtocol::Socks5
    });
    let requires_component = match input.requires_component {
        Some(value) if !value.trim().is_empty() => match parse_component_id(&value) {
            Ok(component) => Some(component),
            Err(err) => {
                errors.push(err);
                None
            }
        },
        _ => None,
    };

    if !errors.is_empty() {
        return Err(errors);
    }

    Ok(Target {
        id: slug(input.id.as_deref().unwrap_or(&name), "target"),
        name,
        kind,
        protocol,
        host,
        port: input.port as u16,
        requires_component,
    })
}
