//! New command implementation - create artifacts.

use crate::NewTarget;
use crate::config::{Config, IdStrategy};
use crate::diagnostic::{Diagnostic, DiagnosticCode};
use crate::model::{
    AdrContent, AdrMeta, AdrSpec, AdrStatus, ChangelogEntry, ClauseKind, ClauseSpec, ClauseStatus,
    ClauseWire, RfcPhase, RfcSpec, RfcStatus, RfcWire, SectionSpec, WorkItemContent, WorkItemMeta,
    WorkItemSpec, WorkItemStatus, WorkItemVerification,
};
use crate::schema::ARTIFACT_SCHEMA_TEMPLATES;
use crate::schema::{ArtifactSchema, with_schema_header};
use crate::ui;
use crate::write::{WriteOp, create_dir_all, today, write_file};
use slug::slugify;

fn schema_version_for_init() -> u32 {
    std::env::var("GOVCTL_SCHEMA_VERSION")
        .ok()
        .and_then(|v| v.parse().ok())
        .unwrap_or(crate::cmd::migrate::CURRENT_SCHEMA_VERSION)
}
use std::path::PathBuf;

/// Skill templates: (relative_path, content) pairs.
/// Source of truth: .claude/skills/; embedded at compile time.
/// Per ADR-0028, all workflow commands are now skills for cross-platform compatibility.
const SKILL_TEMPLATES: &[(&str, &str)] = &[
    (
        "skills/discuss/SKILL.md",
        include_str!("../../.claude/skills/discuss/SKILL.md"),
    ),
    (
        "skills/gov/SKILL.md",
        include_str!("../../.claude/skills/gov/SKILL.md"),
    ),
    (
        "skills/quick/SKILL.md",
        include_str!("../../.claude/skills/quick/SKILL.md"),
    ),
    (
        "skills/spec/SKILL.md",
        include_str!("../../.claude/skills/spec/SKILL.md"),
    ),
    (
        "skills/rfc-writer/SKILL.md",
        include_str!("../../.claude/skills/rfc-writer/SKILL.md"),
    ),
    (
        "skills/adr-writer/SKILL.md",
        include_str!("../../.claude/skills/adr-writer/SKILL.md"),
    ),
    (
        "skills/wi-writer/SKILL.md",
        include_str!("../../.claude/skills/wi-writer/SKILL.md"),
    ),
    (
        "skills/guard-writer/SKILL.md",
        include_str!("../../.claude/skills/guard-writer/SKILL.md"),
    ),
    (
        "skills/commit/SKILL.md",
        include_str!("../../.claude/skills/commit/SKILL.md"),
    ),
    (
        "skills/migrate/SKILL.md",
        include_str!("../../.claude/skills/migrate/SKILL.md"),
    ),
    (
        "skills/decision-analysis/SKILL.md",
        include_str!("../../.claude/skills/decision-analysis/SKILL.md"),
    ),
];

/// Agent templates: (relative_path, content) pairs.
/// Source of truth: .claude/agents/; embedded at compile time.
const AGENT_TEMPLATES: &[(&str, &str)] = &[
    (
        "agents/rfc-reviewer.md",
        include_str!("../../.claude/agents/rfc-reviewer.md"),
    ),
    (
        "agents/adr-reviewer.md",
        include_str!("../../.claude/agents/adr-reviewer.md"),
    ),
    (
        "agents/wi-reviewer.md",
        include_str!("../../.claude/agents/wi-reviewer.md"),
    ),
    (
        "agents/compliance-checker.md",
        include_str!("../../.claude/agents/compliance-checker.md"),
    ),
];

/// All asset templates (skills + agents).
fn all_templates() -> impl Iterator<Item = &'static (&'static str, &'static str)> {
    SKILL_TEMPLATES.iter().chain(AGENT_TEMPLATES.iter())
}

/// Initialize govctl project
pub fn init_project(config: &Config, force: bool, op: WriteOp) -> anyhow::Result<Vec<Diagnostic>> {
    let config_path = config.gov_root.join("config.toml");

    if config_path.exists() && !force && !op.is_preview() {
        return Err(Diagnostic::new(
            DiagnosticCode::E0501ConfigInvalid,
            format!(
                "{} already exists (use -f to overwrite)",
                config_path.display()
            ),
            config_path.display().to_string(),
        )
        .into());
    }

    let dirs: Vec<_> = vec![
        config.gov_root.clone(),
        config.rfc_dir(),
        config.schema_dir(),
        config.rfc_output(),
        config.adr_dir(),
        config.work_dir(),
        config.guard_dir(),
        config.templates_dir(),
    ];

    for dir in &dirs {
        create_dir_all(dir, op, Some(&config.display_path(dir)))?;
        if !op.is_preview() {
            ui::created_path(&config.display_path(dir));
        }
    }

    // Write config after gov_root exists
    write_file(
        &config_path,
        &Config::default_toml(schema_version_for_init()),
        op,
        Some(&config.display_path(&config_path)),
    )?;
    if !op.is_preview() {
        ui::created_path(&config.display_path(&config_path));
    }

    // Install bundled artifact JSON Schemas under gov/schema/.
    let schema_dir = config.schema_dir();
    for template in ARTIFACT_SCHEMA_TEMPLATES {
        let path = schema_dir.join(template.filename);
        let display_path = config.display_path(&path);
        write_file(&path, template.content, op, Some(&display_path))?;
        if !op.is_preview() {
            ui::created_path(&display_path);
        }
    }

    // Ensure .gitignore contains .govctl.lock
    ensure_gitignore_lock_entry(op)?;

    if !op.is_preview() {
        ui::success("Project initialized");
        ui::hint(
            "To install agent skills locally: govctl init-skills\n  \
             Or install the govctl plugin:    /plugin install govctl@govctl",
        );
    }
    Ok(vec![])
}

/// Install agent skills and agents into the project's agent directory. [[ADR-0035]]
pub fn sync_skills(config: &Config, force: bool, op: WriteOp) -> anyhow::Result<Vec<Diagnostic>> {
    let agent_dir = &config.paths.agent_dir;

    let mut synced = 0;
    let mut skipped = 0;

    for (rel_path, template) in all_templates() {
        let path = agent_dir.join(rel_path);
        let display_path = config.display_path(&path);

        // Create parent directory if needed
        if let Some(parent) = path.parent() {
            let display_parent = config.display_path(parent);
            create_dir_all(parent, op, Some(&display_parent))?;
        }

        // Check if file exists and skip if not forcing
        if path.exists() && !force && !op.is_preview() {
            skipped += 1;
            if !op.is_preview() {
                ui::sub_info(format!(
                    "Skipped {} (already exists, use -f to overwrite)",
                    path.display()
                ));
            }
            continue;
        }

        // Write template
        write_file(&path, template, op, Some(&display_path))?;

        if !op.is_preview() {
            if path.exists() && force {
                ui::info(format!("Updated {}", display_path.display()));
            } else {
                ui::created_path(&display_path);
            }
        }
        synced += 1;
    }

    if !op.is_preview() {
        if synced > 0 {
            ui::success(format!("Synced {} asset(s)", synced));
        }
        if skipped > 0 {
            ui::info(format!(
                "{} asset(s) skipped (use -f to overwrite)",
                skipped
            ));
        }
        if synced == 0 && skipped == 0 {
            ui::info("No assets to sync");
        }
    }

    Ok(vec![])
}

/// Create a new artifact
pub fn create(config: &Config, target: &NewTarget, op: WriteOp) -> anyhow::Result<Vec<Diagnostic>> {
    match target {
        NewTarget::Rfc { title, id } => create_rfc(config, title, id.as_deref(), op),
        NewTarget::Clause {
            clause_id,
            title,
            section,
            kind,
        } => create_clause(config, clause_id, title, section, *kind, op),
        NewTarget::Adr { title } => create_adr(config, title, op),
        NewTarget::Work { title, active } => create_work_item(config, title, *active, op),
    }
}

/// Create a new RFC
fn create_rfc(
    config: &Config,
    title: &str,
    manual_id: Option<&str>,
    op: WriteOp,
) -> anyhow::Result<Vec<Diagnostic>> {
    let rfcs_dir = config.rfc_dir();

    // Determine RFC ID: use manual if provided, otherwise auto-generate
    let rfc_id = match manual_id {
        Some(id) => {
            // Validate format
            if !id.starts_with("RFC-") {
                return Err(Diagnostic::new(
                    DiagnosticCode::E0110RfcInvalidId,
                    format!("RFC ID must start with 'RFC-' (got: {id})"),
                    id,
                )
                .into());
            }
            // Check for collision (skip in preview mode)
            if !op.is_preview() && rfcs_dir.join(id).exists() {
                return Err(Diagnostic::new(
                    DiagnosticCode::E0109RfcAlreadyExists,
                    format!("RFC already exists: {id}"),
                    id,
                )
                .into());
            }
            id.to_string()
        }
        None => {
            // Auto-generate: find max RFC number and increment
            let max_num = std::fs::read_dir(&rfcs_dir)
                .into_iter()
                .flatten()
                .flatten()
                .filter_map(|entry| {
                    let name = entry.file_name();
                    let name_str = name.to_string_lossy();
                    name_str
                        .strip_prefix("RFC-")
                        .and_then(|s| s.parse::<u32>().ok())
                })
                .max()
                .unwrap_or(0);

            format!("RFC-{:04}", max_num + 1)
        }
    };

    let rfc_dir = rfcs_dir.join(&rfc_id);
    let clauses_dir = rfc_dir.join("clauses");

    // Final collision check (skip in preview mode)
    if !op.is_preview() && rfc_dir.exists() {
        return Err(Diagnostic::new(
            DiagnosticCode::E0109RfcAlreadyExists,
            format!("RFC already exists: {}", rfc_dir.display()),
            rfc_dir.display().to_string(),
        )
        .into());
    }

    // Create directories
    let display_clauses_dir = config.display_path(&clauses_dir);
    create_dir_all(&clauses_dir, op, Some(&display_clauses_dir))?;

    // Create rfc.toml
    let rfc = RfcSpec {
        rfc_id: rfc_id.to_string(),
        title: title.to_string(),
        version: "0.1.0".to_string(),
        status: RfcStatus::Draft,
        phase: RfcPhase::Spec,
        owners: vec![config.project.default_owner.clone()],
        created: today(),
        updated: None,
        supersedes: None,
        refs: vec![],
        sections: vec![
            SectionSpec {
                title: "Summary".to_string(),
                clauses: vec![],
            },
            SectionSpec {
                title: "Specification".to_string(),
                clauses: vec![],
            },
        ],
        changelog: vec![ChangelogEntry {
            version: "0.1.0".to_string(),
            date: today(),
            notes: Some("Initial draft".to_string()),
            added: vec![],
            changed: vec![],
            deprecated: vec![],
            removed: vec![],
            fixed: vec![],
            security: vec![],
        }],
        signature: None, // Will be set on first bump per [[ADR-0016]]
    };

    let rfc_toml = rfc_dir.join("rfc.toml");
    let wire: RfcWire = rfc.into();
    let body = toml::to_string_pretty(&wire)?;
    let content = with_schema_header(ArtifactSchema::Rfc, &body);
    let display_rfc_toml = config.display_path(&rfc_toml);
    write_file(&rfc_toml, &content, op, Some(&display_rfc_toml))?;

    if !op.is_preview() {
        ui::created("RFC", &config.display_path(&rfc_toml));
        ui::sub_info(format!(
            "Clauses dir: {}",
            config.display_path(&clauses_dir).display()
        ));
    }

    Ok(vec![])
}

/// Create a new clause
fn create_clause(
    config: &Config,
    clause_id: &str,
    title: &str,
    section: &str,
    kind: ClauseKind,
    op: WriteOp,
) -> anyhow::Result<Vec<Diagnostic>> {
    // Parse clause_id (RFC-0001:C-NAME)
    let parts: Vec<&str> = clause_id.split(':').collect();
    if parts.len() != 2 {
        return Err(Diagnostic::new(
            DiagnosticCode::E0210ClauseInvalidIdFormat,
            "Invalid clause ID format. Expected RFC-NNNN:C-NAME",
            clause_id,
        )
        .into());
    }

    let rfc_id = parts[0];
    let clause_name = parts[1];

    let rfc_toml = config.rfc_dir().join(rfc_id).join("rfc.toml");
    let rfc_json = config.rfc_dir().join(rfc_id).join("rfc.json");
    let rfc_path = if rfc_toml.exists() {
        rfc_toml
    } else {
        rfc_json
    };
    if !rfc_path.exists() {
        return Err(Diagnostic::new(
            DiagnosticCode::E0102RfcNotFound,
            format!("RFC not found: {rfc_id}"),
            rfc_id,
        )
        .into());
    }

    let mut rfc = crate::write::read_rfc(config, &rfc_path)?;

    // Create clause
    let clause = ClauseSpec {
        clause_id: clause_name.to_string(),
        title: title.to_string(),
        kind,
        status: ClauseStatus::Active,
        text: "TODO: Add clause text here.".to_string(),
        anchors: vec![],
        superseded_by: None,
        since: None, // Will be set by rfc bump
    };

    let clause_path = config
        .rfc_dir()
        .join(rfc_id)
        .join("clauses")
        .join(format!("{clause_name}.toml"));

    let wire: ClauseWire = clause.into();
    let body = toml::to_string_pretty(&wire)?;
    let content = with_schema_header(ArtifactSchema::Clause, &body);
    let display_clause_path = config.display_path(&clause_path);
    write_file(&clause_path, &content, op, Some(&display_clause_path))?;

    // Update RFC to include clause in section
    let clause_rel_path = format!("clauses/{clause_name}.toml");

    // Find or create section
    if let Some(sec) = rfc.sections.iter_mut().find(|s| s.title == section) {
        if !sec.clauses.contains(&clause_rel_path) {
            sec.clauses.push(clause_rel_path.clone());
        }
    } else {
        rfc.sections.push(SectionSpec {
            title: section.to_string(),
            clauses: vec![clause_rel_path.clone()],
        });
    }

    let rfc_content = match rfc_path.extension().and_then(|ext| ext.to_str()) {
        Some("toml") => {
            let wire: RfcWire = rfc.into();
            let body = toml::to_string_pretty(&wire)?;
            with_schema_header(ArtifactSchema::Rfc, &body)
        }
        _ => serde_json::to_string_pretty(&rfc)?,
    };
    let display_rfc_path = config.display_path(&rfc_path);
    write_file(&rfc_path, &rfc_content, op, Some(&display_rfc_path))?;

    if !op.is_preview() {
        ui::created("clause", &config.display_path(&clause_path));
        ui::sub_info(format!(
            "Added to section '{}', path: {}",
            section, clause_rel_path
        ));
    }

    Ok(vec![])
}

/// Create a new ADR
fn create_adr(config: &Config, title: &str, op: WriteOp) -> anyhow::Result<Vec<Diagnostic>> {
    // Find next ADR number
    let adr_dir = config.adr_dir();
    let display_adr_dir = config.display_path(&adr_dir);
    create_dir_all(&adr_dir, op, Some(&display_adr_dir))?;

    let mut max_num = 0u32;
    if let Ok(entries) = std::fs::read_dir(&adr_dir) {
        for entry in entries.flatten() {
            let name = entry.file_name();
            let name_str = name.to_string_lossy();
            if name_str.starts_with("ADR-")
                && let Some(num_str) = name_str
                    .strip_prefix("ADR-")
                    .and_then(|s| s.split('-').next())
                && let Ok(num) = num_str.parse::<u32>()
            {
                max_num = max_num.max(num);
            }
        }
    }

    let next_num = max_num + 1;
    let adr_id = format!("ADR-{next_num:04}");
    let slug = slugify(title);
    let filename = format!("{adr_id}-{slug}.toml");
    let adr_path = adr_dir.join(&filename);

    // Create ADR spec
    let spec = AdrSpec {
        govctl: AdrMeta {
            schema: 1,
            id: adr_id.clone(),
            title: title.to_string(),
            status: AdrStatus::Proposed,
            date: today(),
            superseded_by: None,
            refs: vec![],
        },
        content: AdrContent {
            context: "Describe the context and problem statement.\nWhat is the issue that we're seeing that is motivating this decision?".to_string(),
            decision: "Describe the decision that was made.\nWhat is the change that we're proposing and/or doing?".to_string(),
            consequences: "Describe the resulting context after applying the decision.\nWhat becomes easier or more difficult to do because of this change?".to_string(),
            alternatives: vec![],
        },
    };

    let body = toml::to_string_pretty(&spec)?;
    let content = with_schema_header(ArtifactSchema::Adr, &body);
    let display_adr_path = config.display_path(&adr_path);
    write_file(&adr_path, &content, op, Some(&display_adr_path))?;

    if !op.is_preview() {
        ui::created("ADR", &config.display_path(&adr_path));
    }

    Ok(vec![])
}

/// Create a new work item
fn create_work_item(
    config: &Config,
    title: &str,
    active: bool,
    op: WriteOp,
) -> anyhow::Result<Vec<Diagnostic>> {
    let work_dir = config.work_dir();
    let display_work_dir = config.display_path(&work_dir);
    create_dir_all(&work_dir, op, Some(&display_work_dir))?;

    let date = today();
    let slug = slugify(title);

    // Generate work item ID based on configured strategy [[ADR-0020]]
    let work_id = match config.work_item.id_strategy {
        IdStrategy::Sequential => {
            // Original behavior: WI-YYYY-MM-DD-NNN
            let id_prefix = format!("WI-{date}-");
            let max_seq = find_max_sequence(&work_dir, &id_prefix);
            format!("WI-{date}-{:03}", max_seq + 1)
        }
        IdStrategy::AuthorHash => {
            // Author-namespaced: WI-YYYY-MM-DD-{hash4}-NNN
            let author_hash = IdStrategy::get_author_hash().unwrap_or_else(|| {
                // Fallback to random if git email not configured
                IdStrategy::generate_random_suffix()
            });
            let id_prefix = format!("WI-{date}-{author_hash}-");
            let max_seq = find_max_sequence(&work_dir, &id_prefix);
            format!("WI-{date}-{author_hash}-{:03}", max_seq + 1)
        }
        IdStrategy::Random => {
            // Random suffix: WI-YYYY-MM-DD-{rand4}
            let random_suffix = IdStrategy::generate_random_suffix();
            format!("WI-{date}-{random_suffix}")
        }
    };

    // Find unique filename (loop until no collision)
    let mut filename = format!("{date}-{slug}.toml");
    let mut work_path = work_dir.join(&filename);
    let mut suffix = 1u32;

    while !op.is_preview() && work_path.exists() {
        filename = format!("{date}-{slug}-{suffix:03}.toml");
        work_path = work_dir.join(&filename);
        suffix += 1;
    }

    // Create work item spec
    let (status, started) = if active {
        (WorkItemStatus::Active, Some(date.clone()))
    } else {
        (WorkItemStatus::Queue, None)
    };

    let spec = WorkItemSpec {
        govctl: WorkItemMeta {
            schema: 1,
            id: work_id.clone(),
            title: title.to_string(),
            status,
            created: Some(date.clone()),
            started,
            completed: None,
            refs: vec![],
        },
        content: WorkItemContent {
            description:
                "Describe the work to be done.\nWhat is the goal? What are the acceptance criteria?"
                    .to_string(),
            journal: vec![],
            acceptance_criteria: vec![],
            notes: vec![],
        },
        verification: WorkItemVerification::default(),
    };

    let body = toml::to_string_pretty(&spec)?;
    let content = with_schema_header(ArtifactSchema::WorkItem, &body);
    let display_work_path = config.display_path(&work_path);
    write_file(&work_path, &content, op, Some(&display_work_path))?;

    if !op.is_preview() {
        let display_path = config.display_path(&work_path);
        ui::created("work item", &display_path);
        ui::sub_info(format!("ID: {work_id}"));
    }

    Ok(vec![])
}

/// Find the maximum sequence number for work items with a given ID prefix.
///
/// Scans all TOML files in `work_dir`, parses their `id` field, and returns
/// the highest sequence number found for IDs starting with `id_prefix`.
fn find_max_sequence(work_dir: &std::path::Path, id_prefix: &str) -> u32 {
    std::fs::read_dir(work_dir)
        .into_iter()
        .flatten()
        .flatten()
        .filter_map(|entry| {
            let path = entry.path();
            (path.extension()? == "toml").then_some(path)
        })
        .filter_map(|path| std::fs::read_to_string(&path).ok())
        .filter_map(|content| {
            content
                .lines()
                .find(|line| line.starts_with("id = \""))
                .and_then(|line| line.strip_prefix("id = \""))
                .and_then(|s| s.strip_suffix('"'))
                .and_then(|id| id.strip_prefix(id_prefix))
                .and_then(|seq_str| seq_str.parse::<u32>().ok())
        })
        .max()
        .unwrap_or(0)
}

/// Ensure .gitignore contains .govctl.lock entry
fn ensure_gitignore_lock_entry(op: WriteOp) -> anyhow::Result<()> {
    const LOCK_ENTRY: &str = ".govctl.lock";
    let gitignore_path = PathBuf::from(".gitignore");

    if gitignore_path.exists() {
        let content = std::fs::read_to_string(&gitignore_path)?;
        // Check if already present
        if content.lines().any(|line| line.trim() == LOCK_ENTRY) {
            return Ok(());
        }
        // Append to existing .gitignore
        let new_content = if content.ends_with('\n') {
            format!("{}{}\n", content, LOCK_ENTRY)
        } else {
            format!("{}\n{}\n", content, LOCK_ENTRY)
        };
        write_file(&gitignore_path, &new_content, op, None)?;
        if !op.is_preview() {
            ui::info(format!("Added '{}' to .gitignore", LOCK_ENTRY));
        }
    } else {
        // Create new .gitignore
        let content = format!("# govctl lock file\n{}\n", LOCK_ENTRY);
        write_file(&gitignore_path, &content, op, None)?;
        if !op.is_preview() {
            ui::created_path(&gitignore_path);
        }
    }
    Ok(())
}
