use super::*;
use std::ffi::OsString;

use crate::provider::{ConversationMessage, ProviderTurnRequest};
use crate::system::{
    CaptureSpec, ExecutionScopeKind, FileHost, ProcessHost, ProcessPurpose, ProcessRequest,
    ProgramInvocation, StdioSpec,
};

impl RuntimeHandle {
    pub(crate) async fn prepare_managed_worktree_for_task(
        &self,
        task_id: &str,
    ) -> Result<ManagedWorktreeSeed> {
        let state = self.agent_state().await?;
        if !state.execution_profile.supports_managed_worktrees {
            return Err(anyhow!(
                "managed worktrees are disabled by the current execution profile"
            ));
        }
        let original_cwd = self.workspace_root();
        let system = self.system();
        let execution = self
            .effective_execution(ExecutionScopeKind::AgentTurn)
            .await?;
        let original_branch_output = system
            .run(
                &execution,
                ProcessRequest {
                    program: ProgramInvocation::Argv {
                        program: "git".into(),
                        args: vec![
                            OsString::from("rev-parse"),
                            OsString::from("--abbrev-ref"),
                            OsString::from("HEAD"),
                        ],
                    },
                    cwd: Some(original_cwd.clone()),
                    env: vec![],
                    stdin: StdioSpec::Null,
                    tty: false,
                    capture: CaptureSpec::BOTH,
                    timeout: None,
                    purpose: ProcessPurpose::InternalGit,
                },
            )
            .await
            .context("failed to determine current git branch")?;
        if !original_branch_output.exit_status.success() {
            let stderr = String::from_utf8_lossy(&original_branch_output.stderr)
                .trim()
                .to_string();
            return Err(anyhow!(
                "failed to determine current git branch for worktree task: {stderr}"
            ));
        }
        let original_branch = String::from_utf8_lossy(&original_branch_output.stdout)
            .trim()
            .to_string();
        if original_branch == "HEAD" {
            return Err(anyhow!(
                "detached HEAD is not supported for worktree subagent tasks"
            ));
        }

        let repo_name = original_cwd
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("repo");
        let managed_root = original_cwd
            .parent()
            .unwrap_or(original_cwd.as_path())
            .join(format!(".holon-worktrees-{repo_name}"));
        system
            .create_dir_all(&execution, &managed_root)
            .await
            .context("failed to create managed worktree directory")?;

        let worktree_branch = format!("task-{task_id}");
        let worktree_path = managed_root.join(&worktree_branch);
        if worktree_path.exists() {
            return Err(anyhow!(
                "managed worktree path already exists: {}",
                worktree_path.display()
            ));
        }

        let worktree_output = system
            .run(
                &execution,
                ProcessRequest {
                    program: ProgramInvocation::Argv {
                        program: "git".into(),
                        args: vec![
                            OsString::from("worktree"),
                            OsString::from("add"),
                            OsString::from("-b"),
                            OsString::from(&worktree_branch),
                            worktree_path.as_os_str().to_os_string(),
                        ],
                    },
                    cwd: Some(original_cwd.clone()),
                    env: vec![],
                    stdin: StdioSpec::Null,
                    tty: false,
                    capture: CaptureSpec::BOTH,
                    timeout: None,
                    purpose: ProcessPurpose::WorktreeSetup,
                },
            )
            .await
            .context("failed to create git worktree")?;
        if !worktree_output.exit_status.success() {
            let stderr = String::from_utf8_lossy(&worktree_output.stderr)
                .trim()
                .to_string();
            return Err(anyhow!("git worktree add failed: {stderr}"));
        }

        self.inner.storage.append_event(&AuditEvent::new(
            "worktree_created_for_task",
            serde_json::json!({
                "task_id": task_id,
                "worktree_path": worktree_path,
                "worktree_branch": worktree_branch,
                "original_cwd": original_cwd,
                "original_branch": original_branch,
            }),
        ))?;

        let seed = ManagedWorktreeSeed {
            original_cwd,
            original_branch,
            worktree_path,
            worktree_branch,
        };
        self.record_task_owned_worktree_metadata(task_id, &seed)
            .await?;
        Ok(seed)
    }

    pub(super) async fn run_subagent_prompt(
        &self,
        agent_id: &str,
        prompt: &str,
        trust: &TrustLevel,
    ) -> Result<String> {
        let execution = self
            .effective_execution(ExecutionScopeKind::SubagentTask)
            .await?;
        self.run_subagent_prompt_for_workspace(
            agent_id,
            prompt,
            trust,
            &execution,
            serde_json::json!({
                "prompt": prompt,
                "trust": trust,
                "execution_root": execution.workspace.execution_root(),
            }),
            None,
        )
        .await
    }

    pub(super) async fn run_subagent_prompt_in_dedicated_worktree(
        &self,
        agent_id: &str,
        prompt: &str,
        trust: &TrustLevel,
        task_id: &str,
    ) -> Result<WorktreeSubagentResult> {
        let seed = self.prepare_managed_worktree_for_task(task_id).await?;
        self.emit_worktree_child_task_running_status(agent_id, task_id, trust)
            .await?;

        let worktree_execution = self
            .effective_execution_for_workspace(
                ExecutionScopeKind::WorktreeSubagentTask,
                self.workspace_view_for_root(
                    seed.worktree_path.clone(),
                    seed.worktree_path.clone(),
                    Some(seed.worktree_path.clone()),
                )?,
            )
            .await?;

        let prompt_result = self
            .run_subagent_prompt_for_workspace(
                agent_id,
                prompt,
                trust,
                &worktree_execution,
                serde_json::json!({
                    "prompt": prompt,
                    "trust": trust,
                    "task_id": task_id,
                    "workspace_root": seed.worktree_path,
                }),
                Some(serde_json::json!({
                    "task_id": task_id,
                    "workspace_root": seed.worktree_path,
                })),
            )
            .await;

        let changed_files = self
            .detect_changed_files(&seed.worktree_path)
            .await
            .unwrap_or_default();

        let (text, failed) = match prompt_result {
            Ok(text) => (text, false),
            Err(err) => (format!("worktree subagent failed: {err:#}"), true),
        };

        Ok(WorktreeSubagentResult {
            text,
            worktree_path: seed.worktree_path,
            worktree_branch: seed.worktree_branch,
            changed_files,
            failed,
        })
    }

    async fn emit_worktree_child_task_running_status(
        &self,
        agent_id: &str,
        task_id: &str,
        trust: &TrustLevel,
    ) -> Result<()> {
        let Some(task) = self.inner.storage.latest_task_record(task_id)? else {
            return Ok(());
        };
        let task_summary = task.summary.clone().unwrap_or_default();
        let running_message = MessageEnvelope {
            metadata: Some(serde_json::json!({
                "task_id": task.id.clone(),
                "task_kind": task.kind.clone(),
                "task_status": "running",
                "task_summary": task.summary.clone(),
                "task_detail": task.detail.clone(),
                "task_recovery": task.recovery.clone(),
            })),
            ..MessageEnvelope::new(
                agent_id.to_string(),
                MessageKind::TaskStatus,
                MessageOrigin::Task {
                    task_id: task_id.to_string(),
                },
                trust.clone(),
                Priority::Background,
                MessageBody::Text {
                    text: format!("worktree child agent task started: {task_summary}"),
                },
            )
            .with_admission(
                MessageDeliverySurface::TaskRejoin,
                AdmissionContext::RuntimeOwned,
            )
        };
        self.enqueue(running_message).await.map(|_| ())
    }

    pub(super) async fn run_subagent_prompt_for_workspace(
        &self,
        agent_id: &str,
        prompt: &str,
        trust: &TrustLevel,
        execution: &crate::system::EffectiveExecution,
        prompt_metadata: serde_json::Value,
        assistant_metadata: Option<serde_json::Value>,
    ) -> Result<String> {
        let effective_prompt = self
            .build_subagent_prompt_for_workspace(agent_id, prompt, trust, execution)
            .await?;
        self.inner
            .storage
            .append_transcript_entry(&TranscriptEntry::new(
                agent_id.to_string(),
                TranscriptEntryKind::SubagentPrompt,
                None,
                None,
                prompt_metadata,
            ))?;
        let response = self
            .current_provider()
            .await
            .complete_turn(ProviderTurnRequest::plain(
                effective_prompt.rendered_system_prompt,
                vec![ConversationMessage::UserText(
                    effective_prompt.rendered_context_attachment,
                )],
                vec![],
            ))
            .await?;

        {
            let mut guard = self.inner.agent.lock().await;
            guard.state.total_input_tokens += response.input_tokens;
            guard.state.total_output_tokens += response.output_tokens;
            guard.state.total_model_rounds += 1;
            guard.state.last_turn_token_usage = Some(crate::types::TokenUsage::new(
                response.input_tokens,
                response.output_tokens,
            ));
            self.inner.storage.write_agent(&guard.state)?;
        }

        self.inner
            .storage
            .append_transcript_entry(&TranscriptEntry {
                stop_reason: response.stop_reason.clone(),
                input_tokens: Some(response.input_tokens),
                output_tokens: Some(response.output_tokens),
                ..TranscriptEntry::new(
                    agent_id.to_string(),
                    TranscriptEntryKind::SubagentAssistantRound,
                    Some(1),
                    None,
                    serde_json::json!({
                        "blocks": response.blocks,
                        "metadata": assistant_metadata,
                    }),
                )
            })?;

        let text = sanitize_subagent_result(
            &response
                .blocks
                .into_iter()
                .filter_map(|block| match block {
                    ModelBlock::Text { text } => Some(text),
                    ModelBlock::ToolUse { .. } => None,
                    ModelBlock::Thinking { .. } | ModelBlock::RedactedThinking { .. } => None,
                })
                .collect::<Vec<_>>()
                .join("\n\n"),
        );
        if text.trim().is_empty() {
            return Ok("subagent completed without textual output".into());
        }
        Ok(text)
    }

    pub(crate) async fn detect_changed_files(&self, worktree_path: &Path) -> Result<Vec<String>> {
        let execution = self
            .effective_execution_for_workspace(
                ExecutionScopeKind::WorktreeSubagentTask,
                self.workspace_view_for_root(
                    worktree_path.to_path_buf(),
                    worktree_path.to_path_buf(),
                    Some(worktree_path.to_path_buf()),
                )?,
            )
            .await?;
        let status_output = self
            .system()
            .run(
                &execution,
                ProcessRequest {
                    program: ProgramInvocation::Argv {
                        program: "git".into(),
                        args: vec![OsString::from("status"), OsString::from("--porcelain")],
                    },
                    cwd: Some(worktree_path.to_path_buf()),
                    env: vec![],
                    stdin: StdioSpec::Null,
                    tty: false,
                    capture: CaptureSpec::BOTH,
                    timeout: None,
                    purpose: ProcessPurpose::InternalGit,
                },
            )
            .await?;

        if status_output.exit_status.success() {
            let output = String::from_utf8_lossy(&status_output.stdout);
            let mut changed_files = output
                .lines()
                .filter(|line| !line.is_empty())
                .map(|line| {
                    let parts = line.trim().splitn(2, ' ').collect::<Vec<_>>();
                    if parts.len() > 1 {
                        parts[1].to_string()
                    } else {
                        line.to_string()
                    }
                })
                .collect::<Vec<_>>();

            changed_files.sort();

            Ok(changed_files)
        } else {
            Ok(Vec::new())
        }
    }
}

pub(super) fn sanitize_subagent_result(text: &str) -> String {
    let mut cleaned = strip_tagged_blocks(text, "think");
    cleaned = strip_xml_like_tool_calls(&cleaned);
    cleaned
        .lines()
        .filter(|line| {
            let trimmed = line.trim();
            !trimmed.starts_with("**[SYSTEM]")
                && !trimmed.starts_with("[SYSTEM]")
                && !trimmed.starts_with("(Executing Step")
                && !trimmed.starts_with("**(Executing Step")
        })
        .map(|line| line.trim_end())
        .collect::<Vec<_>>()
        .join("\n")
        .trim()
        .to_string()
}

fn strip_tagged_blocks(input: &str, tag: &str) -> String {
    let mut remaining = input.to_string();
    let open = format!("<{tag}>");
    let close = format!("</{tag}>");

    while let Some(start) = remaining.find(&open) {
        if let Some(end_rel) = remaining[start..].find(&close) {
            let end = start + end_rel + close.len();
            remaining.replace_range(start..end, "");
        } else {
            remaining.replace_range(start..remaining.len(), "");
            break;
        }
    }

    remaining
}

fn strip_xml_like_tool_calls(input: &str) -> String {
    let mut result = Vec::new();
    let mut skipping = false;

    for line in input.lines() {
        let trimmed = line.trim();
        if trimmed.starts_with('<')
            && trimmed.ends_with('>')
            && trimmed.contains("</")
            && !trimmed.starts_with("</")
        {
            continue;
        }
        if trimmed.starts_with('<')
            && trimmed.ends_with('>')
            && !trimmed.starts_with("</")
            && trimmed.chars().filter(|ch| *ch == '<').count() == 1
        {
            skipping = true;
            continue;
        }
        if skipping {
            if trimmed.starts_with("</") && trimmed.ends_with('>') {
                skipping = false;
            }
            continue;
        }
        result.push(line);
    }

    result.join("\n")
}
