use std::collections::HashSet;
use std::path::Path;
use std::process::ExitCode;

use anyhow::{bail, Result};
use serde::Serialize;

use crate::handoff::{self, BranchMode};
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::state::compiled::{self as compiled_state, BundleProjectionView, BundleSectionKind};
use crate::state::runtime as runtime_state;
use crate::state::session as session_state;

const SESSION_NAME_STOPWORDS: &[&str] = &[
    "a", "an", "and", "for", "from", "in", "into", "of", "on", "or", "the", "to", "with",
];
const SESSION_NAME_GENERIC_TOKENS: &[&str] = &[
    "ccd",
    "child",
    "current",
    "delegated",
    "delegation",
    "next",
    "session",
    "start",
    "task",
    "work",
];
const PARENT_ONLY_COMMANDS: &[&str] = &[
    "ccd session-state start",
    "ccd session-state clear",
    "ccd session-state takeover",
    "ccd handover",
    "ccd checkpoint",
    "ccd handoff write",
    "ccd recovery write",
    "ccd session-state gates seed",
    "ccd session-state gates replace",
    "ccd session-state gates set-status",
    "ccd session-state gates advance",
    "ccd session-state gates clear",
];

#[derive(Serialize)]
pub struct RuntimeStateChildBootstrapReport {
    command: &'static str,
    ok: bool,
    path: String,
    profile: String,
    project_id: String,
    locality_id: String,
    child_bootstrap: DelegatedChildBootstrapView,
}

#[derive(Serialize)]
struct DelegatedChildBootstrapView {
    kind: &'static str,
    mode: &'static str,
    source_fingerprint: String,
    task: String,
    session_name_hint: String,
    current_state: Vec<String>,
    next_focus: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    remember: Vec<String>,
    active_guardrails: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    key_files: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    close_out: Vec<String>,
    shared_workspace: SharedWorkspaceView,
    isolated_workspace: IsolatedWorkspaceView,
    markdown: String,
}

#[derive(Serialize)]
struct SharedWorkspaceView {
    posture: &'static str,
    allowed_actions: Vec<&'static str>,
    parent_only_commands: Vec<&'static str>,
}

#[derive(Serialize)]
struct IsolatedWorkspaceView {
    status: &'static str,
    posture: &'static str,
    happy_path: Vec<String>,
}

struct MarkdownSections<'a> {
    current_state: &'a [String],
    next_focus: &'a [String],
    remember: &'a [String],
    active_guardrails: &'a [String],
    key_files: &'a [String],
    close_out: &'a [String],
    shared_workspace: &'a SharedWorkspaceView,
    isolated_workspace: &'a IsolatedWorkspaceView,
}

impl CommandReport for RuntimeStateChildBootstrapReport {
    fn exit_code(&self) -> ExitCode {
        ExitCode::SUCCESS
    }

    fn render_text(&self) {
        print!("{}", self.child_bootstrap.markdown);
    }
}

pub fn run(
    repo_root: &Path,
    explicit_profile: Option<&str>,
) -> Result<RuntimeStateChildBootstrapReport> {
    let profile = profile::resolve(explicit_profile)?;
    let layout = StateLayout::resolve(repo_root, profile.clone())?;
    ensure_profile_exists(&layout, repo_root)?;
    let locality_id = ensure_repo_linked(repo_root)?;
    let runtime = runtime_state::load_runtime_state(repo_root, &layout, &locality_id)?;
    let bundle = compiled_state::preview_bundle_for_target_with_cache(
        &layout,
        &runtime,
        compiled_state::ProjectionTarget::Default,
    )?
    .value;
    let tracked_session = session_state::load_for_layout(&layout)?;
    let git = handoff::read_git_state(repo_root, BranchMode::AllowDetachedHead).ok();

    let title = if runtime.state.handoff.title.trim().is_empty() {
        format!("Next Session: {}", bundle.task)
    } else {
        runtime.state.handoff.title.trim().to_owned()
    };
    let current_state = build_current_state(&title, git.as_ref(), tracked_session.as_ref());
    let next_focus = build_next_focus(&bundle);
    let remember = bundle_section_items(&bundle, BundleSectionKind::Remember);
    let key_files = bundle_section_items(&bundle, BundleSectionKind::Files);
    let close_out = bundle_section_items(&bundle, BundleSectionKind::CloseOut);
    let active_guardrails = build_guardrails(&bundle);
    let session_name_hint = derive_session_name(&bundle.task, &next_focus);
    let isolated_workspace = build_isolated_workspace_view(git.as_ref(), &session_name_hint);
    let shared_workspace = SharedWorkspaceView {
        posture: "read_only_investigation_only",
        allowed_actions: vec![
            "Consume this bootstrap bundle instead of rerunning `ccd start` in the shared workspace.",
            "Inspect files and run non-mutating repo tools to gather findings or draft patches.",
            "Return bounded results to the parent for integration and canonical CCD follow-through.",
        ],
        parent_only_commands: PARENT_ONLY_COMMANDS.to_vec(),
    };
    let markdown = render_markdown(
        &bundle.task,
        &MarkdownSections {
            current_state: &current_state,
            next_focus: &next_focus,
            remember: &remember,
            active_guardrails: &active_guardrails,
            key_files: &key_files,
            close_out: &close_out,
            shared_workspace: &shared_workspace,
            isolated_workspace: &isolated_workspace,
        },
    );

    Ok(RuntimeStateChildBootstrapReport {
        command: "runtime-state-child-bootstrap",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_string(),
        project_id: locality_id.clone(),
        locality_id,
        child_bootstrap: DelegatedChildBootstrapView {
            kind: "delegated_child_bootstrap",
            mode: "host_driven",
            source_fingerprint: bundle.source_fingerprint,
            task: bundle.task,
            session_name_hint,
            current_state,
            next_focus,
            remember,
            active_guardrails,
            key_files,
            close_out,
            shared_workspace,
            isolated_workspace,
            markdown,
        },
    })
}

fn build_current_state(
    title: &str,
    git: Option<&handoff::GitState>,
    tracked_session: Option<&session_state::SessionStateFile>,
) -> Vec<String> {
    let mut current_state = vec![format!("Title: {title}")];
    let session_id = tracked_session.and_then(|state| state.session_id.as_deref());
    let live_state = handoff::current_system_state_lines(git, session_id);
    let mode_line = tracked_session
        .filter(|state| state.mode != session_state::SessionMode::General)
        .map(|session| format!("Session mode: `{}`", session.mode.as_str()));

    if let Some(mode_line) = mode_line {
        current_state.extend(live_state.into_iter().take(1));
        current_state.push(mode_line);
    } else {
        current_state.extend(live_state.into_iter().take(2));
    }
    current_state
}

fn build_next_focus(bundle: &BundleProjectionView) -> Vec<String> {
    let mut items = bundle_section_items(bundle, BundleSectionKind::Do);
    items.extend(bundle_section_items(bundle, BundleSectionKind::Focus));
    let mut items = dedupe_preserving_order(items);
    if items.len() > 4 {
        items.truncate(4);
    }
    items
}

fn build_guardrails(bundle: &BundleProjectionView) -> Vec<String> {
    let mut items = bundle_section_items(bundle, BundleSectionKind::Rules);
    items.extend([
        "Parent remains the authoritative owner of workspace-local continuity, handoff, recovery, and session telemetry.".to_owned(),
        "Shared-workspace children stay read-only; protected CCD writes remain parent-only in this workspace.".to_owned(),
        "Any delegated child that needs repo writes must use an isolated workspace with its own CCD state.".to_owned(),
        "Parent integrates child results and performs canonical `ccd handover` or `ccd handoff write` follow-through.".to_owned(),
    ]);
    let mut items = dedupe_preserving_order(items);
    if items.len() > 6 {
        items.truncate(6);
    }
    items
}

fn build_isolated_workspace_view(
    git: Option<&handoff::GitState>,
    session_name_hint: &str,
) -> IsolatedWorkspaceView {
    if git.is_some() {
        return IsolatedWorkspaceView {
            status: "available",
            posture: "required_for_repo_writes",
            happy_path: vec![
                format!(
                    "Open a sibling workspace with `ccd session open --path . --profile <profile> --worktree ../{session_name_hint} --branch session/{session_name_hint} --from <trunk>` before delegated repo writes."
                ),
                "Run `ccd start --activate` inside that isolated workspace only.".to_owned(),
                "Let the parent integrate the child result and perform canonical close-out in the parent workspace.".to_owned(),
            ],
        };
    }

    IsolatedWorkspaceView {
        status: "unsupported",
        posture: "shared_workspace_only",
        happy_path: vec![
            "This workspace is not Git-backed, so `ccd session open` is unavailable here.".to_owned(),
            "Keep delegated children read-only in the shared workspace until a separate non-Git isolation path exists.".to_owned(),
            "Parent remains responsible for canonical CCD follow-through in the current workspace.".to_owned(),
        ],
    }
}

fn bundle_section_items(bundle: &BundleProjectionView, kind: BundleSectionKind) -> Vec<String> {
    bundle
        .sections
        .iter()
        .find(|section| section.kind == kind)
        .map(|section| section.items.clone())
        .unwrap_or_default()
}

fn dedupe_preserving_order(items: Vec<String>) -> Vec<String> {
    let mut seen = HashSet::new();
    let mut deduped = Vec::new();
    for item in items {
        let marker = normalize_space(&item).to_ascii_lowercase();
        if marker.is_empty() || !seen.insert(marker) {
            continue;
        }
        deduped.push(item);
    }
    deduped
}

fn normalize_space(text: &str) -> String {
    text.split_whitespace().collect::<Vec<_>>().join(" ")
}

fn derive_session_name(task: &str, next_focus: &[String]) -> String {
    let source = if task.trim().is_empty() {
        next_focus
            .first()
            .map(String::as_str)
            .unwrap_or("session focus")
    } else {
        task
    };
    let mut tokens = tokenize(source);
    tokens.retain(|token| !SESSION_NAME_STOPWORDS.contains(&token.as_str()));
    let meaningful = tokens
        .iter()
        .filter(|token| !SESSION_NAME_GENERIC_TOKENS.contains(&token.as_str()))
        .cloned()
        .collect::<Vec<_>>();
    let chosen = if meaningful.is_empty() {
        tokens
    } else {
        meaningful
    };
    let name = chosen.into_iter().take(3).collect::<Vec<_>>().join("-");
    if name.is_empty() {
        "session-focus".to_owned()
    } else {
        name
    }
}

fn tokenize(source: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    let mut current = String::new();
    for ch in source.chars() {
        if ch.is_ascii_alphanumeric() {
            current.push(ch.to_ascii_lowercase());
        } else if !current.is_empty() {
            tokens.push(std::mem::take(&mut current));
        }
    }
    if !current.is_empty() {
        tokens.push(current);
    }
    tokens
}

fn render_markdown(task: &str, sections: &MarkdownSections<'_>) -> String {
    let mut body = String::from("# CCD Delegated Child Bootstrap\n\n");
    body.push_str(&format!("Task: {task}\n\n"));
    push_markdown_section(&mut body, "Current state", sections.current_state);
    push_markdown_section(&mut body, "Next focus", sections.next_focus);
    push_markdown_section(&mut body, "Remember", sections.remember);
    push_markdown_section(&mut body, "Active guardrails", sections.active_guardrails);
    push_markdown_section(&mut body, "Files", sections.key_files);
    push_markdown_section(&mut body, "Close-out", sections.close_out);
    let mut shared_items = vec![format!("Posture: `{}`", sections.shared_workspace.posture)];
    shared_items.extend(
        sections
            .shared_workspace
            .allowed_actions
            .iter()
            .map(|item| (*item).to_owned()),
    );
    shared_items.push(format!(
        "Parent-only commands: {}",
        sections.shared_workspace.parent_only_commands.join(", ")
    ));
    push_markdown_section(&mut body, "Shared workspace", &shared_items);
    let mut isolated_items = vec![format!("Status: `{}`", sections.isolated_workspace.status)];
    isolated_items.push(format!(
        "Posture: `{}`",
        sections.isolated_workspace.posture
    ));
    isolated_items.extend(sections.isolated_workspace.happy_path.clone());
    push_markdown_section(&mut body, "Isolated workspace", &isolated_items);
    body
}

fn push_markdown_section(body: &mut String, title: &str, items: &[String]) {
    if items.is_empty() {
        return;
    }
    body.push_str(&format!("## {title}\n"));
    for item in items {
        body.push_str("- ");
        body.push_str(item);
        body.push('\n');
    }
    body.push('\n');
}

fn ensure_profile_exists(layout: &StateLayout, repo_root: &Path) -> Result<()> {
    let profile_root = layout.profile_root();
    if profile_root.is_dir() {
        return Ok(());
    }

    bail!(
        "profile `{}` does not exist at {}; bootstrap it with `ccd attach --path {}` before using `ccd runtime-state child-bootstrap`",
        layout.profile(),
        profile_root.display(),
        repo_root.display()
    )
}

fn ensure_repo_linked(repo_root: &Path) -> Result<String> {
    let Some(marker) = repo_marker::load(repo_root)? else {
        bail!(
            "repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
            repo_root.join(repo_marker::MARKER_FILE).display(),
            repo_root.display(),
            repo_root.display()
        )
    };

    Ok(marker.locality_id)
}
