use std::{
    fs,
    path::PathBuf,
    sync::atomic::{AtomicU64, Ordering},
    time::{SystemTime, UNIX_EPOCH},
};

use serde_json::json;

use crate::runtime::{RuntimeStore, SqliteRuntimeStore, TaskIntrinsicTool};

use super::{
    TaskAccess, TaskItem,
    input::{
        TaskCreateInput, TaskUpdateInput, parse_task_create_input, parse_task_list_input,
        parse_task_update_input,
    },
    types::TaskStatus,
};

static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(1);

#[test]
fn create_and_list_group_ready_blocked_and_completed_tasks() {
    let store = TaskHarness::new("grouping");

    store.create(TaskCreateInput {
        subject: "Plan".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "Build".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });
    store.create(TaskCreateInput {
        subject: "Review".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.update(
        parse_task_update_input(json!({
            "taskId": 3,
            "status": "in_progress"
        }))
        .expect("parse update"),
        TaskAccess::Lead,
    );
    store.update(
        parse_task_update_input(json!({
            "taskId": 1,
            "status": "completed"
        }))
        .expect("parse update"),
        TaskAccess::Lead,
    );

    let listed = serde_json::from_str::<serde_json::Value>(&store.list()).expect("parse output");
    assert_eq!(listed["ready"].as_array().expect("ready").len(), 1);
    assert_eq!(listed["blocked"].as_array().expect("blocked").len(), 0);
    assert_eq!(
        listed["inProgress"].as_array().expect("in progress").len(),
        1
    );
    assert_eq!(listed["completed"].as_array().expect("completed").len(), 1);
}

#[test]
fn completion_unblocks_and_reopen_reblocks_dependents() {
    let store = TaskHarness::new("reblock");

    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });

    let completed = serde_json::from_str::<serde_json::Value>(
        &store.update(
            parse_task_update_input(json!({
                "taskId": 1,
                "status": "completed"
            }))
            .expect("parse update"),
            TaskAccess::Lead,
        ),
    )
    .expect("parse completed");
    assert_eq!(
        completed["unblocked"].as_array().expect("unblocked").len(),
        1
    );

    let reopened = serde_json::from_str::<serde_json::Value>(
        &store.update(
            parse_task_update_input(json!({
                "taskId": 1,
                "status": "pending"
            }))
            .expect("parse update"),
            TaskAccess::Lead,
        ),
    )
    .expect("parse reopened");
    assert_eq!(
        reopened["reblocked"].as_array().expect("reblocked").len(),
        1
    );
}

#[test]
fn adding_cycle_is_rejected() {
    let store = TaskHarness::new("cycle");

    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });

    let error = store
        .try_update(
            parse_task_update_input(json!({
                "taskId": 1,
                "addBlockedBy": [2]
            }))
            .expect("parse update"),
            TaskAccess::Lead,
        )
        .expect_err("cycle should fail");
    assert!(error.contains("would create a cycle"));
}

#[test]
fn blocked_task_cannot_start_or_complete() {
    let store = TaskHarness::new("blocked-status");

    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });

    let error = store
        .try_update(
            parse_task_update_input(json!({
                "taskId": 2,
                "status": "in_progress"
            }))
            .expect("parse update"),
            TaskAccess::Lead,
        )
        .expect_err("blocked task should fail");
    assert!(error.contains("cannot be in_progress while blocked"));
}

#[test]
fn parse_helpers_reject_bad_input() {
    assert!(parse_task_create_input(json!({ "subject": "" })).is_err());
    assert!(parse_task_update_input(json!({ "taskId": 1, "bogus": true })).is_err());
    assert!(parse_task_list_input(json!({ "bogus": true })).is_err());
}

#[test]
fn completed_blocker_stays_out_of_unresolved_blocked_by() {
    let store = TaskHarness::new("completed-blocker");

    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.update(
        parse_task_update_input(json!({
            "taskId": 1,
            "status": "completed"
        }))
        .expect("parse update"),
        TaskAccess::Lead,
    );
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });

    let tasks = store.load_all();
    assert_eq!(tasks[1].status, TaskStatus::Pending);
    assert!(tasks[1].blocked_by.is_empty());
    assert_eq!(tasks[0].blocks, vec![2]);
}

#[test]
fn claim_first_ready_unowned_task() {
    let store = TaskHarness::new("claim-first");
    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });

    let claimed = serde_json::from_str::<serde_json::Value>(&store.claim(None, "alice"))
        .expect("parse claimed");
    assert_eq!(claimed["id"].as_u64(), Some(1));
    assert_eq!(claimed["owner"].as_str(), Some("alice"));
}

#[test]
fn claim_explicit_task_id() {
    let store = TaskHarness::new("claim-explicit");
    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });

    let claimed = serde_json::from_str::<serde_json::Value>(&store.claim(Some(2), "bob"))
        .expect("parse claimed");
    assert_eq!(claimed["id"].as_u64(), Some(2));
    assert_eq!(claimed["owner"].as_str(), Some("bob"));
}

#[test]
fn claim_rejects_unclaimable_tasks() {
    let store = TaskHarness::new("claim-reject");
    store.create(TaskCreateInput {
        subject: "A".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "B".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: vec![1],
    });

    let blocked = store.try_claim(Some(2), "alice").expect_err("blocked task");
    assert!(blocked.contains("cannot be claimed"));

    store.claim(Some(1), "alice");
    let owned = store.try_claim(Some(1), "bob").expect_err("owned task");
    assert!(owned.contains("already owned"));

    let missing = store
        .try_claim(Some(99), "alice")
        .expect_err("missing task");
    assert!(missing.contains("does not exist"));

    let store = TaskHarness::new("claim-status");
    store.create(TaskCreateInput {
        subject: "C".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.update(
        parse_task_update_input(json!({
            "taskId": 1,
            "status": "in_progress"
        }))
        .expect("parse update"),
        TaskAccess::Lead,
    );
    let in_progress = store
        .try_claim(Some(1), "alice")
        .expect_err("in progress task");
    assert!(in_progress.contains("cannot be claimed"));

    let store = TaskHarness::new("claim-completed");
    store.create(TaskCreateInput {
        subject: "D".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.update(
        parse_task_update_input(json!({
            "taskId": 1,
            "status": "completed"
        }))
        .expect("parse update"),
        TaskAccess::Lead,
    );
    let completed = store
        .try_claim(Some(1), "alice")
        .expect_err("completed task");
    assert!(completed.contains("cannot be claimed"));
}

#[test]
fn teammate_cannot_edit_task_dependencies() {
    let store = TaskHarness::new("teammate-deps");
    store.create(TaskCreateInput {
        subject: "Owned".to_string(),
        description: String::new(),
        owner: "alice".to_string(),
        working_directory: None,
        blocked_by: Vec::new(),
    });
    store.create(TaskCreateInput {
        subject: "Other".to_string(),
        description: String::new(),
        owner: String::new(),
        working_directory: None,
        blocked_by: Vec::new(),
    });

    let error = store
        .try_update(
            parse_task_update_input(json!({
                "taskId": 1,
                "addBlocks": [2]
            }))
            .expect("parse update"),
            TaskAccess::Teammate("alice"),
        )
        .expect_err("dependency edit should fail");
    assert!(error.contains("cannot edit dependencies"));
}

struct TaskHarness {
    store: SqliteRuntimeStore,
    namespace: PathBuf,
}

impl TaskHarness {
    fn new(label: &str) -> Self {
        Self {
            store: temp_store(label),
            namespace: temp_namespace(label),
        }
    }

    fn create(&self, input: TaskCreateInput) -> String {
        self.try_create(input).expect("create task")
    }

    fn try_create(&self, input: TaskCreateInput) -> Result<String, String> {
        super::execute_with_store(
            &self.store,
            &super::TaskIntrinsicTool::Create,
            serde_json::to_value(input).expect("serialize task create input"),
            self.namespace.as_path(),
            TaskAccess::Lead,
        )
    }

    fn update(&self, input: TaskUpdateInput, access: TaskAccess<'_>) -> String {
        self.try_update(input, access).expect("update task")
    }

    fn try_update(&self, input: TaskUpdateInput, access: TaskAccess<'_>) -> Result<String, String> {
        super::execute_with_store(
            &self.store,
            &super::TaskIntrinsicTool::Update,
            serde_json::to_value(input).expect("serialize task update input"),
            self.namespace.as_path(),
            access,
        )
    }

    fn claim(&self, task_id: Option<u64>, owner: &str) -> String {
        self.try_claim(task_id, owner).expect("claim task")
    }

    fn try_claim(&self, task_id: Option<u64>, owner: &str) -> Result<String, String> {
        super::execute_with_store(
            &self.store,
            &TaskIntrinsicTool::Claim,
            json!({ "taskId": task_id }),
            self.namespace.as_path(),
            TaskAccess::Teammate(owner),
        )
    }

    fn list(&self) -> String {
        super::execute_with_store(
            &self.store,
            &TaskIntrinsicTool::List,
            json!({}),
            self.namespace.as_path(),
            TaskAccess::Lead,
        )
        .expect("list tasks")
    }

    fn load_all(&self) -> Vec<TaskItem> {
        self.store
            .load_tasks(self.namespace.as_path())
            .expect("load tasks")
    }
}

fn temp_namespace(label: &str) -> PathBuf {
    let path = temp_path(format!("mentra-task-graph-{label}"));
    fs::create_dir_all(&path).expect("create temp namespace dir");
    path
}

fn temp_store(label: &str) -> SqliteRuntimeStore {
    SqliteRuntimeStore::new(
        temp_path(format!("mentra-task-store-{label}")).with_extension("sqlite"),
    )
}

fn temp_path(label: String) -> PathBuf {
    let unique = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time")
        .as_nanos();
    std::env::temp_dir().join(format!("{label}-{timestamp}-{unique}"))
}
