use std::fs;
use std::path::PathBuf;

use chrono::{TimeZone, Utc};
use cucumber::{given, then};

use kanbus::file_io::load_project_directory;
use kanbus::models::IssueData;

use crate::step_definitions::initialization_steps::KanbusWorld;

fn load_project_dir(world: &KanbusWorld) -> PathBuf {
    let cwd = world.working_directory.as_ref().expect("cwd");
    load_project_directory(cwd).expect("project dir")
}

fn local_project_dir(world: &KanbusWorld) -> PathBuf {
    let project_dir = load_project_dir(world);
    let local_dir = project_dir
        .parent()
        .expect("project parent")
        .join("project-local");
    fs::create_dir_all(local_dir.join("issues")).expect("create local issues");
    local_dir
}

fn write_issue_file(project_dir: &PathBuf, issue: &IssueData) {
    let issue_path = project_dir
        .join("issues")
        .join(format!("{}.json", issue.identifier));
    let contents = serde_json::to_string_pretty(issue).expect("serialize issue");
    fs::write(issue_path, contents).expect("write issue");
}

fn build_issue(identifier: &str, title: &str) -> IssueData {
    let timestamp = Utc.with_ymd_and_hms(2026, 2, 11, 0, 0, 0).unwrap();
    IssueData {
        identifier: identifier.to_string(),
        title: title.to_string(),
        description: "".to_string(),
        issue_type: "task".to_string(),
        status: "open".to_string(),
        priority: 2,
        assignee: None,
        creator: None,
        parent: None,
        labels: Vec::new(),
        dependencies: Vec::new(),
        comments: Vec::new(),
        created_at: timestamp,
        updated_at: timestamp,
        closed_at: None,
        custom: std::collections::BTreeMap::new(),
    }
}

#[given("a local issue \"kanbus-local01\" exists")]
fn given_local_issue_exists(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue = build_issue("kanbus-local01", "Local");
    write_issue_file(&local_dir, &issue);
}

#[given("a local issue \"kanbus-dupe01\" exists")]
fn given_local_issue_dupe_exists(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue = build_issue("kanbus-dupe01", "Local");
    write_issue_file(&local_dir, &issue);
}

#[given("a local issue \"kanbus-other\" exists")]
fn given_local_issue_other_exists(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue = build_issue("kanbus-other", "Local");
    write_issue_file(&local_dir, &issue);
}

#[given("a local issue \"kanbus-dupe02\" exists")]
fn given_local_issue_dupe02_exists(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue = build_issue("kanbus-dupe02", "Local");
    write_issue_file(&local_dir, &issue);
}

#[given("a local issue \"kanbus-local\" exists")]
fn given_local_issue_local_exists(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue = build_issue("kanbus-local", "Local");
    write_issue_file(&local_dir, &issue);
}

#[given(".gitignore already includes \"project-local/\"")]
fn given_gitignore_includes_project_local(world: &mut KanbusWorld) {
    let project_dir = load_project_dir(world);
    let gitignore_path = project_dir
        .parent()
        .expect("project parent")
        .join(".gitignore");
    fs::write(gitignore_path, "project-local/\n").expect("write gitignore");
}

#[given("a .gitignore without a trailing newline exists")]
fn given_gitignore_without_trailing_newline(world: &mut KanbusWorld) {
    let project_dir = load_project_dir(world);
    let gitignore_path = project_dir
        .parent()
        .expect("project parent")
        .join(".gitignore");
    fs::write(gitignore_path, "node_modules").expect("write gitignore");
}

#[then("a local issue file should be created in the local issues directory")]
fn then_local_issue_file_created(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issues_dir = local_dir.join("issues");
    let count = fs::read_dir(&issues_dir)
        .expect("read local issues")
        .filter(|entry| {
            let Ok(item) = entry else {
                return false;
            };
            let path = item.path();
            path.extension()
                .and_then(|ext| ext.to_str())
                .map(|ext| ext == "json")
                .unwrap_or(false)
        })
        .count();
    assert_eq!(count, 1);
}

#[then(expr = "the local issues directory should contain {int} issue file")]
fn then_local_issues_directory_contains_count(world: &mut KanbusWorld, count: i32) {
    let local_dir = local_project_dir(world);
    let issues_dir = local_dir.join("issues");
    let actual = fs::read_dir(&issues_dir)
        .expect("read local issues")
        .filter(|entry| {
            let Ok(item) = entry else {
                return false;
            };
            let path = item.path();
            path.extension()
                .and_then(|ext| ext.to_str())
                .map(|ext| ext == "json")
                .unwrap_or(false)
        })
        .count();
    assert_eq!(actual as i32, count);
}

#[then("issue \"kanbus-local01\" should exist in the shared issues directory")]
fn then_issue_exists_shared_local(world: &mut KanbusWorld) {
    let project_dir = load_project_dir(world);
    let issue_path = project_dir.join("issues").join("kanbus-local01.json");
    assert!(issue_path.exists());
}

#[then("issue \"kanbus-local01\" should not exist in the local issues directory")]
fn then_issue_missing_local_local(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue_path = local_dir.join("issues").join("kanbus-local01.json");
    assert!(!issue_path.exists());
}

#[then("issue \"kanbus-shared01\" should exist in the local issues directory")]
fn then_issue_exists_local_shared(world: &mut KanbusWorld) {
    let local_dir = local_project_dir(world);
    let issue_path = local_dir.join("issues").join("kanbus-shared01.json");
    assert!(issue_path.exists());
}

#[then("issue \"kanbus-shared01\" should not exist in the shared issues directory")]
fn then_issue_missing_shared_shared(world: &mut KanbusWorld) {
    let project_dir = load_project_dir(world);
    let issue_path = project_dir.join("issues").join("kanbus-shared01.json");
    assert!(!issue_path.exists());
}

#[then(".gitignore should include \"project-local/\"")]
fn then_gitignore_includes_project_local(world: &mut KanbusWorld) {
    let project_dir = load_project_dir(world);
    let gitignore_path = project_dir
        .parent()
        .expect("project parent")
        .join(".gitignore");
    let contents = fs::read_to_string(gitignore_path).expect("read gitignore");
    assert!(contents.lines().any(|line| line == "project-local/"));
}

#[then(".gitignore should include \"project-local/\" only once")]
fn then_gitignore_includes_once(world: &mut KanbusWorld) {
    let project_dir = load_project_dir(world);
    let gitignore_path = project_dir
        .parent()
        .expect("project parent")
        .join(".gitignore");
    let contents = fs::read_to_string(gitignore_path).expect("read gitignore");
    let count = contents
        .lines()
        .filter(|line| *line == "project-local/")
        .count();
    assert_eq!(count, 1);
}
