use dioxus::prelude::*;

use crate::components::page_header::PageHeader;
use crate::infrastructure::onboarding::{
    create_target, detect_target, enable_target_scans, fetch_applicable_scans, trigger_target_scan,
    upload_target_artifact, validate_artifact_ref, validate_target_name, ArtifactInputDto,
};

/// (value, label, one-line description) for the 9 target families.
const TARGET_TYPES: &[(&str, &str, &str)] = &[
    ("web_app", "Web Application", "Front end + server"),
    ("backend_service", "Backend / API", "REST, GraphQL, gRPC"),
    ("desktop_app", "Desktop App", "Windows / macOS / Linux"),
    ("android_app", "Android App", "APK / AAB"),
    ("ios_app", "iOS App", "IPA"),
    (
        "firmware_bare_metal",
        "Firmware — bare metal",
        "No operating system",
    ),
    ("firmware_rtos", "Firmware — RTOS", "Zephyr, FreeRTOS, ..."),
    (
        "embedded_linux_yocto",
        "Embedded Linux / Yocto",
        "BSP + image",
    ),
    ("plc_sps", "PLC / SPS", "IEC 61131-3"),
];

/// (value, label) for the artifact kinds a user can attach.
const ARTIFACT_KINDS: &[(&str, &str)] = &[
    ("git_repo", "Git repository"),
    ("source_archive", "Source archive (zip)"),
    ("firmware_image", "Firmware image"),
    ("mobile_package", "Mobile package (APK/IPA)"),
    ("container_image", "Container image"),
    ("live_url", "Live URL"),
    ("plc_project", "PLC project"),
    ("plaintext_description", "Description (text)"),
];

const STEP_LABELS: &[&str] = &["Target type", "Artifacts", "Review", "Done"];

/// Artifact kinds provided as an uploaded file (rather than a URL/text ref).
fn is_file_kind(kind: &str) -> bool {
    matches!(
        kind,
        "plc_project" | "firmware_image" | "source_archive" | "mobile_package"
    )
}

/// A file artifact staged in the wizard, uploaded after the target is created.
#[derive(Clone, PartialEq)]
struct PendingFile {
    kind: String,
    plc_format: Option<String>,
    filename: String,
    bytes: Vec<u8>,
}

/// One row in the applicable-scans list on the success step.
#[component]
fn ScanRow(scan: serde_json::Value) -> Element {
    let name = scan
        .get("scan")
        .and_then(|v| v.as_str())
        .unwrap_or("?")
        .to_string();
    let rationale = scan
        .get("rationale")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let blocked = scan
        .get("blocked_reason")
        .and_then(|v| v.as_str())
        .map(String::from);
    let default_on = scan
        .get("default_on")
        .and_then(|v| v.as_bool())
        .unwrap_or(false);
    let badge_class = if blocked.is_some() {
        "badge badge-info"
    } else if default_on {
        "badge badge-success"
    } else {
        "badge"
    };
    rsx! {
        div { style: "display: flex; gap: 8px; align-items: center; padding: 6px 0;",
            span { class: "{badge_class}", "{name}" }
            span { style: "opacity: 0.8;", "{rationale}" }
            if let Some(b) = blocked {
                span { style: "opacity: 0.6; font-style: italic;", "— {b}" }
            }
        }
    }
}

fn kind_label(kind: &str) -> &str {
    ARTIFACT_KINDS
        .iter()
        .find(|(v, _)| *v == kind)
        .map(|(_, l)| *l)
        .unwrap_or(kind)
}

fn type_label(value: &str) -> &str {
    TARGET_TYPES
        .iter()
        .find(|(v, _, _)| *v == value)
        .map(|(_, l, _)| *l)
        .unwrap_or(value)
}

#[component]
pub fn OnboardingPage() -> Element {
    let mut step = use_signal(|| 0usize);
    let mut name = use_signal(String::new);
    let mut target_type = use_signal(String::new);
    let mut description = use_signal(String::new);
    let mut artifacts = use_signal(Vec::<ArtifactInputDto>::new);

    // "Add artifact" mini-form.
    let mut new_kind = use_signal(|| "git_repo".to_string());
    let mut new_source = use_signal(String::new);
    let mut new_branch = use_signal(|| "main".to_string());
    // File-upload artifacts (PLC project, firmware image, ...).
    let mut new_plc_format = use_signal(|| "plcopen_xml".to_string());
    let mut new_file = use_signal(|| Option::<(String, Vec<u8>)>::None);
    let mut pending_files = use_signal(Vec::<PendingFile>::new);

    // Create + result state.
    let mut creating = use_signal(|| false);
    let mut error = use_signal(|| Option::<String>::None);
    let mut scans = use_signal(Vec::<serde_json::Value>::new);
    let mut suggested = use_signal(|| Option::<String>::None);
    let mut created_id = use_signal(|| Option::<String>::None);
    let mut scan_msg = use_signal(|| Option::<String>::None);
    // Opt-in scans (default-off but unblocked) the user ticks to enable before
    // running — stored as serde scan-type names (lowercase, no underscores).
    let mut enabled_extra = use_signal(Vec::<String>::new);

    let step_now = step();
    let name_error = validate_target_name(&name());
    let can_advance_type = name_error.is_none() && !target_type().trim().is_empty();
    let has_artifacts = !artifacts().is_empty() || !pending_files().is_empty();
    // Opt-in scans: applicable + unblocked, but default-off (e.g. the ICS probe).
    // The user ticks these to enable them before the first run. Each entry is
    // (display name for the label, serde scan-type name for the enable call —
    // lowercase, no underscores, matching ScanType's rename_all = "lowercase").
    let optin_scans: Vec<(String, String)> = scans()
        .iter()
        .filter_map(|s| {
            let unblocked = s.get("blocked_reason").and_then(|v| v.as_str()).is_none();
            let default_on = s
                .get("default_on")
                .and_then(|v| v.as_bool())
                .unwrap_or(false);
            if unblocked && !default_on {
                let display = s.get("scan").and_then(|v| v.as_str())?.to_string();
                let serde_name = display.replace('_', "");
                Some((display, serde_name))
            } else {
                None
            }
        })
        .collect();
    // Live validation of the artifact reference being typed (empty = no error yet).
    let new_source_error = if new_source().is_empty() {
        None
    } else {
        validate_artifact_ref(&new_kind(), &new_source())
    };

    rsx! {
        PageHeader {
            title: "Onboard a target",
            description: "Add a target, attach its artifacts, and see which scans apply.",
        }

        // Stepper.
        div { class: "wizard-steps",
            for (i, label) in STEP_LABELS.iter().enumerate() {
                div {
                    class: if i == step_now { "wizard-step wizard-step-active" } else { "wizard-step" },
                    span { class: "wizard-step-dot", "{i + 1}" }
                    span { class: "wizard-step-label", "{label}" }
                }
            }
        }

        if let Some(err) = error() {
            div { class: "card", style: "border-color: var(--danger, #d33); margin-bottom: 12px;",
                div { class: "card-header", "Error" }
                div { style: "padding: 12px;", "{err}" }
            }
        }

        div { class: "card",
            // ---- Step 0: target type + name ----
            if step_now == 0 {
                div { class: "card-header", "What kind of software is this?" }
                div { style: "padding: 16px;",
                    div { class: "form-group",
                        label { "Name" }
                        input {
                            r#type: "text",
                            placeholder: "acme-web",
                            value: "{name}",
                            oninput: move |e| name.set(e.value()),
                        }
                        if !name().is_empty() {
                            if let Some(err) = name_error.clone() {
                                div { style: "color: var(--danger, #d33); font-size: 0.85em; margin-top: 4px;", "{err}" }
                            }
                        }
                    }
                    div {
                        style: "display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-top: 12px;",
                        for (value, tlabel, tdesc) in TARGET_TYPES.iter().copied() {
                            div {
                                class: "card",
                                style: if target_type() == value {
                                    "padding: 12px; cursor: pointer; border: 2px solid var(--accent, #3b82f6);"
                                } else {
                                    "padding: 12px; cursor: pointer;"
                                },
                                onclick: move |_| target_type.set(value.to_string()),
                                div { style: "font-weight: 600;", "{tlabel}" }
                                div { style: "font-size: 0.85em; opacity: 0.7;", "{tdesc}" }
                            }
                        }
                    }
                }
            }

            // ---- Step 1: artifacts ----
            if step_now == 1 {
                div { class: "card-header", "Attach artifacts" }
                if target_type() == "plc_sps" {
                    div {
                        style: "margin: 12px 16px 0; padding: 12px 14px; border-left: 3px solid var(--accent, #3b82f6); background: var(--surface-2, rgba(59,130,246,0.08)); font-size: 0.88em; line-height: 1.55;",
                        div { style: "font-weight: 600; margin-bottom: 4px;", "CODESYS / PLC projects" }
                        "Attach a "
                        b { "PLC project" }
                        " (PLCopen XML / ST, or a .projectarchive), or a "
                        b { "Git repository" }
                        " of exported source — every scan is then just a pull."
                        ul { style: "margin: 6px 0 0; padding-left: 18px;",
                            li {
                                b { "Control-logic SAST" }
                                " — commit "
                                b { "PLCopen XML exports" }
                                " (Project → Export PLCopenXML) or raw .st; ST and graphical FBD/LD are both analyzed."
                            }
                            li {
                                b { "Library + runtime SBOM" }
                                " — include the "
                                b { ".projectarchive" }
                                "; PLCopen XML alone carries no libraries."
                            }
                            li {
                                "Avoid committing only the binary "
                                code { ".project" }
                                " — it can't be parsed and doesn't diff."
                            }
                        }
                    }
                }
                div { style: "padding: 16px;",
                    div { style: "display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-end;",
                        div { class: "form-group", style: "margin: 0;",
                            label { "Kind" }
                            select {
                                value: "{new_kind}",
                                oninput: move |e| new_kind.set(e.value()),
                                for (value, klabel) in ARTIFACT_KINDS.iter().copied() {
                                    option { value: "{value}", "{klabel}" }
                                }
                            }
                        }
                        if is_file_kind(&new_kind()) {
                            div { class: "form-group", style: "margin: 0; flex: 1; min-width: 240px;",
                                label { "File" }
                                input {
                                    r#type: "file",
                                    onchange: move |evt| {
                                        let Some(file) = evt.files().into_iter().next() else { return; };
                                        let name = file.name();
                                        // Auto-detect the PLC format from the file extension.
                                        let lname = name.to_ascii_lowercase();
                                        if lname.ends_with(".projectarchive") || lname.ends_with(".project") {
                                            new_plc_format.set("project_archive".to_string());
                                        } else if lname.ends_with(".xml") || lname.ends_with(".plcopen") {
                                            new_plc_format.set("plcopen_xml".to_string());
                                        } else if lname.ends_with(".st") || lname.ends_with(".exp") || lname.ends_with(".scl") {
                                            new_plc_format.set("structured_text".to_string());
                                        }
                                        spawn(async move {
                                            if let Ok(bytes) = file.read_bytes().await {
                                                new_file.set(Some((name, bytes.to_vec())));
                                            }
                                        });
                                    },
                                }
                            }
                            if new_kind() == "plc_project" {
                                div { class: "form-group", style: "margin: 0;",
                                    label { "Format" }
                                    select {
                                        value: "{new_plc_format}",
                                        oninput: move |e| new_plc_format.set(e.value()),
                                        option {
                                            value: "plcopen_xml",
                                            selected: new_plc_format() == "plcopen_xml",
                                            "PLCopen XML",
                                        }
                                        option {
                                            value: "structured_text",
                                            selected: new_plc_format() == "structured_text",
                                            "Structured Text",
                                        }
                                        option {
                                            value: "project_archive",
                                            selected: new_plc_format() == "project_archive",
                                            "Project archive (.projectarchive)",
                                        }
                                    }
                                }
                            }
                            button {
                                class: "btn btn-secondary",
                                disabled: new_file().is_none(),
                                onclick: move |_| {
                                    if let Some((fname, data)) = new_file() {
                                        let kind = new_kind();
                                        let plc_format = if kind == "plc_project" {
                                            Some(new_plc_format())
                                        } else {
                                            None
                                        };
                                        pending_files.write().push(PendingFile {
                                            kind,
                                            plc_format,
                                            filename: fname,
                                            bytes: data,
                                        });
                                        new_file.set(None);
                                    }
                                },
                                "+ Add file"
                            }
                        } else {
                            div { class: "form-group", style: "margin: 0; flex: 1; min-width: 240px;",
                                label { "Reference (URL / path / text)" }
                                input {
                                    r#type: "text",
                                    placeholder: "https://git.example.com/acme.git",
                                    value: "{new_source}",
                                    oninput: move |e| new_source.set(e.value()),
                                }
                            }
                            if new_kind() == "git_repo" {
                                div { class: "form-group", style: "margin: 0;",
                                    label { "Branch" }
                                    input {
                                        r#type: "text",
                                        value: "{new_branch}",
                                        oninput: move |e| new_branch.set(e.value()),
                                    }
                                }
                            }
                            button {
                                class: "btn btn-secondary",
                                disabled: new_source().trim().is_empty() || new_source_error.is_some(),
                                onclick: move |_| {
                                    let kind = new_kind();
                                    if !new_source().trim().is_empty()
                                        && validate_artifact_ref(&kind, &new_source()).is_none()
                                    {
                                        let branch = if kind == "git_repo" { Some(new_branch()) } else { None };
                                        artifacts.write().push(ArtifactInputDto {
                                            kind,
                                            source_ref: new_source(),
                                            branch,
                                            plc_format: None,
                                        });
                                        new_source.set(String::new());
                                    }
                                },
                                "+ Add"
                            }
                        }
                    }
                    if is_file_kind(&new_kind()) {
                        if let Some((fname, data)) = new_file() {
                            div { style: "font-size: 0.85em; opacity: 0.7; margin-top: 6px;",
                                "Selected: {fname} ({data.len()} bytes)"
                            }
                        }
                    } else if let Some(err) = new_source_error.clone() {
                        div { style: "color: var(--danger, #d33); font-size: 0.85em; margin-top: 6px;", "{err}" }
                    }
                    // Staged file artifacts (uploaded after the target is created).
                    for (i, pf) in pending_files().iter().enumerate() {
                        div {
                            style: "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border: 1px solid var(--border, #333); border-radius: 6px; margin-top: 6px;",
                            span {
                                span { style: "opacity: 0.7;", "{kind_label(&pf.kind)} (file): " }
                                "{pf.filename} ({pf.bytes.len()} bytes)"
                            }
                            button {
                                class: "btn btn-ghost-danger btn-sm",
                                onclick: move |_| { pending_files.write().remove(i); },
                                "Remove"
                            }
                        }
                    }

                    div { style: "margin-top: 16px;",
                        if has_artifacts {
                            for (i, a) in artifacts().iter().enumerate() {
                                div {
                                    style: "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border: 1px solid var(--border, #333); border-radius: 6px; margin-bottom: 6px;",
                                    span {
                                        span { style: "opacity: 0.7;", "{kind_label(&a.kind)}: " }
                                        "{a.source_ref}"
                                    }
                                    button {
                                        class: "btn btn-ghost-danger btn-sm",
                                        onclick: move |_| { artifacts.write().remove(i); },
                                        "Remove"
                                    }
                                }
                            }
                        } else {
                            div { style: "opacity: 0.6;", "No artifacts yet. Add at least one." }
                        }
                    }
                }
            }

            // ---- Step 2: review + create ----
            if step_now == 2 {
                div { class: "card-header", "Review" }
                div { style: "padding: 16px;",
                    div { class: "wizard-summary",
                        div { strong { "Name: " } "{name()}" }
                        div { strong { "Type: " } "{type_label(&target_type())}" }
                        div { strong { "Artifacts:" } }
                        ul {
                            for a in artifacts() {
                                li { "{kind_label(&a.kind)}: {a.source_ref}" }
                            }
                        }
                    }
                    div { style: "margin-top: 12px; opacity: 0.7; font-size: 0.9em;",
                        "The applicable scans (SAST / DAST / firmware / PLC) are shown after the target is created."
                    }
                }
            }

            // ---- Step 3: created ----
            if step_now == 3 {
                div { class: "card-header", "Target onboarded" }
                div { style: "padding: 16px;",
                    p {
                        strong { "{name()}" }
                        " was created."
                        if let Some(s) = suggested() {
                            span { " Suggested type from detection: " strong { "{type_label(&s)}" } "." }
                        }
                    }
                    h4 { style: "margin-top: 16px;", "Applicable scans" }
                    if scans().is_empty() {
                        div { style: "opacity: 0.6;", "No scans available (no code / URL / firmware artifact present)." }
                    } else {
                        for s in scans() {
                            ScanRow { scan: s }
                        }
                    }
                    if !optin_scans.is_empty() {
                        div { style: "margin-top: 12px; padding: 10px; border: 1px dashed var(--border, #ccc); border-radius: 6px;",
                            div { style: "font-weight: 600; margin-bottom: 6px;", "Enable opt-in scans" }
                            div { style: "opacity: 0.7; font-size: 0.85em; margin-bottom: 8px;",
                                "These are applicable but off by default (they touch a live device). Tick to enable before running."
                            }
                            for pair in optin_scans.clone() {
                                {
                                    let (display, serde_name) = pair;
                                    let cb_name = serde_name.clone();
                                    rsx! {
                                        label {
                                            style: "display: flex; gap: 6px; align-items: center; margin-top: 4px;",
                                            input {
                                                r#type: "checkbox",
                                                checked: enabled_extra().contains(&serde_name),
                                                onchange: move |_| {
                                                    let mut v = enabled_extra();
                                                    if let Some(p) = v.iter().position(|x| x == &cb_name) {
                                                        v.remove(p);
                                                    } else {
                                                        v.push(cb_name.clone());
                                                    }
                                                    enabled_extra.set(v);
                                                },
                                            }
                                            "Enable {display}"
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if let Some(msg) = scan_msg() {
                        div { style: "margin-top: 8px; color: var(--success, #2a2);", "{msg}" }
                    }
                    div { style: "margin-top: 16px; display: flex; gap: 8px;",
                        button {
                            class: "btn btn-primary",
                            onclick: move |_| {
                                if let Some(id) = created_id() {
                                    let extra = enabled_extra();
                                    scan_msg.set(Some("Scan triggered...".to_string()));
                                    spawn(async move {
                                        // Persist any ticked opt-in scans first, so the
                                        // agent's build_scan_plan includes them this run.
                                        if !extra.is_empty() {
                                            if let Err(e) =
                                                enable_target_scans(id.clone(), extra).await
                                            {
                                                scan_msg.set(Some(format!(
                                                    "Failed to enable opt-in scans: {e}"
                                                )));
                                                return;
                                            }
                                        }
                                        match trigger_target_scan(id).await {
                                            Ok(_) => scan_msg.set(Some(
                                                "Scan started — findings will appear as it runs.".to_string(),
                                            )),
                                            Err(e) => scan_msg.set(Some(format!("Failed to start scan: {e}"))),
                                        }
                                    });
                                }
                            },
                            "Run scan"
                        }
                        button {
                            class: "btn btn-secondary",
                            onclick: move |_| {
                                step.set(0);
                                name.set(String::new());
                                target_type.set(String::new());
                                description.set(String::new());
                                artifacts.write().clear();
                                pending_files.write().clear();
                                new_file.set(None);
                                scans.write().clear();
                                suggested.set(None);
                                created_id.set(None);
                                scan_msg.set(None);
                                error.set(None);
                            },
                            "Onboard another"
                        }
                    }
                }
            }
        }

        // ---- Footer navigation ----
        if step_now < 3 {
            div { style: "display: flex; justify-content: space-between; margin-top: 16px;",
                button {
                    class: "btn btn-back",
                    disabled: step_now == 0,
                    onclick: move |_| { if step() > 0 { step.set(step() - 1); } },
                    "Back"
                }
                if step_now < 2 {
                    button {
                        class: "btn btn-primary",
                        disabled: (step_now == 0 && !can_advance_type) || (step_now == 1 && !has_artifacts),
                        onclick: move |_| step.set(step() + 1),
                        "Next"
                    }
                } else {
                    button {
                        class: "btn btn-primary",
                        disabled: creating(),
                        onclick: move |_| {
                            let n = name();
                            let tt = target_type();
                            let desc = description();
                            let arts = artifacts();
                            let files = pending_files();
                            let d = if desc.trim().is_empty() { None } else { Some(desc) };
                            creating.set(true);
                            error.set(None);
                            spawn(async move {
                                match create_target(n, tt, d, arts).await {
                                    Ok(resp) => {
                                        let id = resp
                                            .data
                                            .get("_id")
                                            .and_then(|o| o.get("$oid"))
                                            .and_then(|s| s.as_str())
                                            .map(String::from);
                                        if let Some(id) = id {
                                            created_id.set(Some(id.clone()));
                                            // Upload staged file artifacts now that the target exists.
                                            for pf in files {
                                                let fname = pf.filename.clone();
                                                if let Err(e) = upload_target_artifact(
                                                    id.clone(),
                                                    pf.kind,
                                                    pf.plc_format,
                                                    pf.filename,
                                                    pf.bytes,
                                                )
                                                .await
                                                {
                                                    error.set(Some(format!(
                                                        "Upload failed for {fname}: {e}"
                                                    )));
                                                }
                                            }
                                            if let Ok(sc) = fetch_applicable_scans(id.clone()).await {
                                                scans.set(sc.data.scans);
                                            }
                                            if let Ok(det) = detect_target(id).await {
                                                suggested.set(
                                                    det.data
                                                        .get("classification")
                                                        .and_then(|c| c.get("suggested"))
                                                        .and_then(|s| s.as_str())
                                                        .map(String::from),
                                                );
                                            }
                                        }
                                        step.set(3);
                                    }
                                    Err(e) => error.set(Some(e.to_string())),
                                }
                                creating.set(false);
                            });
                        },
                        if creating() { "Creating..." } else { "Create target" }
                    }
                }
            }
        }
    }
}
