use std::path::PathBuf;

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::error::Result;

/// Capabilities reported by a VCS backend.
///
/// Protocol layers (`jjw`, agent coordinator) use these flags to decide what
/// operations are safe rather than switching on the backend name.
#[derive(Clone, Debug)]
pub struct VcsCapabilities {
    /// jj: true — conflicts are first-class data, merges always produce a
    /// commit. git: false — merge conflict causes a hard error.
    pub conflicts_as_data: bool,
    /// jj: true — change_id survives rebase; safe to use as persistent
    /// identifier. git: false — SHA changes on every rebase.
    pub stable_change_ids: bool,
    /// jj: true — `jj workspace add/forget` are native operations.
    /// git: false — worktrees exist but are a different abstraction.
    pub native_workspaces: bool,
    /// jj: false — no hook system.
    /// git: true — pre-commit, post-merge, etc.
    pub hooks_supported: bool,
}

/// VCS-specific metadata stored per workspace.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum VcsMetadata {
    Jj {
        /// Stable change ID — survives rebase, safe as persistent reference.
        change_id: String,
        /// jj workspace name used with `jj workspace forget`.
        workspace_name: String,
    },
    Git {
        /// Branch name (`agent/<agent_id>/<workspace_id>`).
        branch: String,
        /// HEAD commit SHA at workspace creation time.
        commit_sha: String,
    },
}

/// Data transfer object returned by backend operations.
///
/// Has no mutation methods — use [`crate::handle::WorktreeHandle`] for
/// activation state.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WorkspaceInfo {
    pub id: String,
    pub agent_id: String,
    pub path: PathBuf,
    pub vcs: VcsMetadata,
    pub created_at: DateTime<Utc>,
}

/// A file-level conflict detected during merge preview.
#[derive(Clone, Debug, Default)]
pub struct ConflictInfo {
    pub path: PathBuf,
    pub description: String,
}

/// Result of a merge preview.
///
/// Semantics differ by backend — see [`WorkspaceBackend::check_merge`].
#[derive(Debug)]
pub struct MergeStatus {
    /// git: false if dry-run detected conflicts. jj: always true (conflicts are
    /// data).
    pub can_merge: bool,
    pub conflicts: Vec<ConflictInfo>,
    /// Files under `.ontology/` modified in this workspace.
    /// The protocol layer decides if these are blocking.
    pub ontology_files_modified: Vec<PathBuf>,
}

/// VCS backend abstraction for workspace lifecycle operations.
///
/// Use `Box<dyn WorkspaceBackend>` — every operation spawns a subprocess so
/// vtable overhead is negligible, and a single concrete `WorktreeManager` type
/// avoids propagating type parameters to callers.
#[async_trait]
pub trait WorkspaceBackend: Send + Sync + 'static {
    fn name(&self) -> &'static str;
    fn capabilities(&self) -> VcsCapabilities;

    async fn create_workspace(&self, agent_id: &str, workspace_id: &str) -> Result<WorkspaceInfo>;

    /// Preview merge readiness.
    ///
    /// **git**: destructive dry-run (`git merge --no-commit`) then rollback
    /// (`git merge --abort`). The working tree is temporarily mutated — not
    /// atomic if the process dies between the two steps.
    ///
    /// **jj**: non-destructive. Reports only which `.ontology/` files are
    /// modified. Conflicts are not detected upfront since jj stores them as
    /// data in the merge commit.
    async fn check_merge(&self, workspace_id: &str, target: &str) -> Result<MergeStatus>;

    /// Execute the merge. Does not remove the workspace — call
    /// `remove_workspace` after.
    async fn merge_workspace(&self, workspace_id: &str, target: &str) -> Result<()>;

    async fn remove_workspace(&self, workspace_id: &str) -> Result<()>;

    /// Workspaces visible to the VCS that live under the managed workspace
    /// base.
    async fn list_workspaces(&self) -> Result<Vec<WorkspaceInfo>>;

    async fn push_to_remote(&self, workspace_id: &str, agent_id: &str, remote: &str) -> Result<()>;

    async fn cleanup_orphaned(&self) -> Result<()>;
}
