//! Template & synchronisation use cases (ARCHITECTURE §6, §8; L7).
//!
//! Two concerns live here:
//! - **Templates** (global IDE store): CRUD + monotonic versioning
//!   ([`CreateTemplate`], [`UpdateTemplate`], [`ListTemplates`], [`DeleteTemplate`]).
//! - **Template → agent link**: instantiating an agent from a template
//!   ([`CreateAgentFromTemplate`]), detecting when a synchronized agent is behind
//!   its template ([`DetectAgentDrift`]), and applying the update
//!   ([`SyncAgentWithTemplate`]).
//!
//! Every use case talks only to ports ([`TemplateStore`], [`AgentContextStore`],
//! [`IdGenerator`], [`EventBus`]).

use std::sync::Arc;

use domain::ports::{AgentContextStore, EventBus, IdGenerator, StoreError, TemplateStore};
use domain::{
    Agent, AgentId, AgentManifest, AgentOrigin, AgentTemplate, DomainEvent, ManifestEntry,
    MarkdownDoc, ProfileId, Project, TemplateId, TemplateVersion,
};

use crate::agent::unique_md_path;
use crate::error::AppError;

// ---------------------------------------------------------------------------
// CreateTemplate
// ---------------------------------------------------------------------------

/// Input for [`CreateTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateTemplateInput {
    /// Display name.
    pub name: String,
    /// Initial Markdown content.
    pub content: String,
    /// Default runtime profile for agents created from this template.
    pub default_profile_id: ProfileId,
}

/// Output of [`CreateTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateTemplateOutput {
    /// The created template (at [`TemplateVersion::INITIAL`]).
    pub template: AgentTemplate,
}

/// Creates a template in the global store at the initial version.
pub struct CreateTemplate {
    templates: Arc<dyn TemplateStore>,
    ids: Arc<dyn IdGenerator>,
}

impl CreateTemplate {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(templates: Arc<dyn TemplateStore>, ids: Arc<dyn IdGenerator>) -> Self {
        Self { templates, ids }
    }

    /// Executes creation.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] if the name is empty,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(
        &self,
        input: CreateTemplateInput,
    ) -> Result<CreateTemplateOutput, AppError> {
        let id = TemplateId::from_uuid(self.ids.new_uuid());
        let template = AgentTemplate::new(
            id,
            input.name,
            MarkdownDoc::new(input.content),
            input.default_profile_id,
        )
        .map_err(|e| AppError::Invalid(e.to_string()))?;
        self.templates.save(&template).await?;
        Ok(CreateTemplateOutput { template })
    }
}

// ---------------------------------------------------------------------------
// UpdateTemplate
// ---------------------------------------------------------------------------

/// Input for [`UpdateTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateTemplateInput {
    /// Template to update.
    pub template_id: TemplateId,
    /// New Markdown content.
    pub content: String,
}

/// Output of [`UpdateTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateTemplateOutput {
    /// The updated template (version bumped by one).
    pub template: AgentTemplate,
}

/// Updates a template's content and **bumps its version** (monotonic, §8.1),
/// announcing [`DomainEvent::TemplateUpdated`] so drift can be re-evaluated.
pub struct UpdateTemplate {
    templates: Arc<dyn TemplateStore>,
    events: Arc<dyn EventBus>,
}

impl UpdateTemplate {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(templates: Arc<dyn TemplateStore>, events: Arc<dyn EventBus>) -> Self {
        Self { templates, events }
    }

    /// Executes the update.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if the template is unknown,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(
        &self,
        input: UpdateTemplateInput,
    ) -> Result<UpdateTemplateOutput, AppError> {
        let current = self.templates.get(input.template_id).await?;
        let updated = current.with_updated_content(MarkdownDoc::new(input.content));
        self.templates.save(&updated).await?;
        self.events.publish(DomainEvent::TemplateUpdated {
            template_id: updated.id,
            version: updated.version,
        });
        Ok(UpdateTemplateOutput { template: updated })
    }
}

// ---------------------------------------------------------------------------
// ReadTemplate / ListTemplates / DeleteTemplate
// ---------------------------------------------------------------------------

/// Input for [`ReadTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadTemplateInput {
    /// Template to read.
    pub template_id: TemplateId,
}

/// Output of [`ReadTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadTemplateOutput {
    /// The requested template.
    pub template: AgentTemplate,
}

/// Reads one template from the global store.
pub struct ReadTemplate {
    templates: Arc<dyn TemplateStore>,
}

impl ReadTemplate {
    /// Builds the use case.
    #[must_use]
    pub fn new(templates: Arc<dyn TemplateStore>) -> Self {
        Self { templates }
    }

    /// Reads one template.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if the template is unknown,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: ReadTemplateInput) -> Result<ReadTemplateOutput, AppError> {
        Ok(ReadTemplateOutput {
            template: self.templates.get(input.template_id).await?,
        })
    }
}

/// Output of [`ListTemplates::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListTemplatesOutput {
    /// All templates in the global store.
    pub templates: Vec<AgentTemplate>,
}

/// Lists the templates in the global store.
pub struct ListTemplates {
    templates: Arc<dyn TemplateStore>,
}

impl ListTemplates {
    /// Builds the use case.
    #[must_use]
    pub fn new(templates: Arc<dyn TemplateStore>) -> Self {
        Self { templates }
    }

    /// Lists templates.
    ///
    /// # Errors
    /// [`AppError::Store`] on persistence failure.
    pub async fn execute(&self) -> Result<ListTemplatesOutput, AppError> {
        Ok(ListTemplatesOutput {
            templates: self.templates.list().await?,
        })
    }
}

/// Input for [`DeleteTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteTemplateInput {
    /// Template to delete.
    pub template_id: TemplateId,
}

/// Deletes a template from the global store.
///
/// Agents previously created from it keep their `.md` (their `origin` still
/// references the now-absent template; drift detection simply finds nothing to
/// compare against).
pub struct DeleteTemplate {
    templates: Arc<dyn TemplateStore>,
}

impl DeleteTemplate {
    /// Builds the use case.
    #[must_use]
    pub fn new(templates: Arc<dyn TemplateStore>) -> Self {
        Self { templates }
    }

    /// Deletes the template.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if the template is unknown,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: DeleteTemplateInput) -> Result<(), AppError> {
        self.templates.delete(input.template_id).await?;
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// CreateAgentFromTemplate
// ---------------------------------------------------------------------------

/// Input for [`CreateAgentFromTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateAgentFromTemplateInput {
    /// The project that owns the agent.
    pub project: Project,
    /// Source template.
    pub template_id: TemplateId,
    /// Optional agent name; defaults to the template's name.
    pub name: Option<String>,
    /// Whether the agent tracks the template (`true` ⇒ future syncs apply).
    pub synchronized: bool,
}

/// Output of [`CreateAgentFromTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateAgentFromTemplateOutput {
    /// The created agent.
    pub agent: Agent,
}

/// Instantiates a project agent from a template: copies the template's
/// `content_md` into the agent's `.md`, links the origin to the template at its
/// current version, and records the manifest entry.
pub struct CreateAgentFromTemplate {
    templates: Arc<dyn TemplateStore>,
    contexts: Arc<dyn AgentContextStore>,
    ids: Arc<dyn IdGenerator>,
    events: Arc<dyn EventBus>,
}

impl CreateAgentFromTemplate {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(
        templates: Arc<dyn TemplateStore>,
        contexts: Arc<dyn AgentContextStore>,
        ids: Arc<dyn IdGenerator>,
        events: Arc<dyn EventBus>,
    ) -> Self {
        Self {
            templates,
            contexts,
            ids,
            events,
        }
    }

    /// Executes creation from a template.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if the template is unknown,
    /// - [`AppError::Invalid`] if the resulting agent/manifest is invalid,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(
        &self,
        input: CreateAgentFromTemplateInput,
    ) -> Result<CreateAgentFromTemplateOutput, AppError> {
        let template = self.templates.get(input.template_id).await?;
        let manifest = self.contexts.load_manifest(&input.project).await?;

        let name = input.name.unwrap_or_else(|| template.name.clone());
        let id = AgentId::from_uuid(self.ids.new_uuid());
        let md_path = unique_md_path(&name, &manifest);
        let origin = AgentOrigin::FromTemplate {
            template_id: template.id,
            synced_template_version: template.version,
        };
        let agent = Agent::new(
            id,
            name,
            md_path,
            template.default_profile_id,
            origin,
            input.synchronized,
        )
        .map_err(|e| AppError::Invalid(e.to_string()))?;

        let mut entries = manifest.entries;
        entries.push(ManifestEntry::from_agent(&agent));
        let manifest = AgentManifest::new(manifest.version, entries)
            .map_err(|e| AppError::Invalid(e.to_string()))?;
        self.contexts
            .save_manifest(&input.project, &manifest)
            .await?;

        // Seed the agent context with the template content.
        self.contexts
            .write_context(&input.project, &agent.id, &template.content_md)
            .await?;

        self.events.publish(DomainEvent::LayoutChanged {
            project_id: input.project.id,
        });

        Ok(CreateAgentFromTemplateOutput { agent })
    }
}

// ---------------------------------------------------------------------------
// DetectAgentDrift
// ---------------------------------------------------------------------------

/// One drifting agent: its template moved ahead of the agent's synced version.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentDrift {
    /// The drifting agent.
    pub agent_id: AgentId,
    /// Version the agent is currently synced to.
    pub from: TemplateVersion,
    /// Version available from the template.
    pub to: TemplateVersion,
}

/// Input for [`DetectAgentDrift::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetectAgentDriftInput {
    /// The project whose agents to check.
    pub project: Project,
}

/// Output of [`DetectAgentDrift::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetectAgentDriftOutput {
    /// Agents whose template has a newer version (only `synchronized` ones).
    pub drifts: Vec<AgentDrift>,
}

/// Detects which synchronized agents are behind their template
/// (`template.version > synced_template_version`, §8.2), announcing
/// [`DomainEvent::AgentDriftDetected`] for each.
pub struct DetectAgentDrift {
    templates: Arc<dyn TemplateStore>,
    contexts: Arc<dyn AgentContextStore>,
    events: Arc<dyn EventBus>,
}

impl DetectAgentDrift {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(
        templates: Arc<dyn TemplateStore>,
        contexts: Arc<dyn AgentContextStore>,
        events: Arc<dyn EventBus>,
    ) -> Self {
        Self {
            templates,
            contexts,
            events,
        }
    }

    /// Computes the drift set.
    ///
    /// # Errors
    /// [`AppError::Store`] on persistence failure (a *deleted* template is not an
    /// error — that agent simply has nothing to drift against).
    pub async fn execute(
        &self,
        input: DetectAgentDriftInput,
    ) -> Result<DetectAgentDriftOutput, AppError> {
        let manifest = self.contexts.load_manifest(&input.project).await?;
        let mut drifts = Vec::new();
        for entry in &manifest.entries {
            // Only synchronized, template-backed agents can drift.
            if !entry.synchronized {
                continue;
            }
            let (Some(template_id), Some(synced)) =
                (entry.template_id, entry.synced_template_version)
            else {
                continue;
            };
            let template = match self.templates.get(template_id).await {
                Ok(t) => t,
                Err(StoreError::NotFound) => continue,
                Err(e) => return Err(e.into()),
            };
            if template.version > synced {
                let drift = AgentDrift {
                    agent_id: entry.agent_id,
                    from: synced,
                    to: template.version,
                };
                self.events.publish(DomainEvent::AgentDriftDetected {
                    agent_id: drift.agent_id,
                    from: drift.from,
                    to: drift.to,
                });
                drifts.push(drift);
            }
        }
        Ok(DetectAgentDriftOutput { drifts })
    }
}

// ---------------------------------------------------------------------------
// SyncAgentWithTemplate
// ---------------------------------------------------------------------------

/// Input for [`SyncAgentWithTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SyncAgentWithTemplateInput {
    /// The owning project.
    pub project: Project,
    /// The agent to bring up to date.
    pub agent_id: AgentId,
}

/// Output of [`SyncAgentWithTemplate::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SyncAgentWithTemplateOutput {
    /// Whether an update was applied (`false` for a non-synchronized or
    /// scratch agent — those are intentionally left untouched, §8.4).
    pub synced: bool,
    /// The version the agent is now at, when a sync happened.
    pub version: Option<TemplateVersion>,
}

/// Applies a template update to a synchronized agent: **replaces** the agent's
/// `.md` with the template content (the context of a synchronized agent is
/// "owned" by the template, §8.3) and records the new synced version. Agents
/// that are not `synchronized` (or are `scratch`) are left untouched.
pub struct SyncAgentWithTemplate {
    templates: Arc<dyn TemplateStore>,
    contexts: Arc<dyn AgentContextStore>,
    events: Arc<dyn EventBus>,
}

impl SyncAgentWithTemplate {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(
        templates: Arc<dyn TemplateStore>,
        contexts: Arc<dyn AgentContextStore>,
        events: Arc<dyn EventBus>,
    ) -> Self {
        Self {
            templates,
            contexts,
            events,
        }
    }

    /// Executes the sync for one agent.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if the agent or its template is unknown,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(
        &self,
        input: SyncAgentWithTemplateInput,
    ) -> Result<SyncAgentWithTemplateOutput, AppError> {
        let mut manifest = self.contexts.load_manifest(&input.project).await?;
        let idx = manifest
            .entries
            .iter()
            .position(|e| e.agent_id == input.agent_id)
            .ok_or_else(|| AppError::NotFound(format!("agent {}", input.agent_id)))?;
        let entry = &manifest.entries[idx];

        // Non-synchronized / scratch agents are never auto-updated (§8.4).
        let Some(template_id) = entry.template_id.filter(|_| entry.synchronized) else {
            return Ok(SyncAgentWithTemplateOutput {
                synced: false,
                version: None,
            });
        };

        let template = self.templates.get(template_id).await?;
        manifest.entries[idx].synced_template_version = Some(template.version);

        // Persist the manifest (revalidated) and overwrite the agent context.
        let manifest = AgentManifest::new(manifest.version, manifest.entries)
            .map_err(|e| AppError::Invalid(e.to_string()))?;
        self.contexts
            .save_manifest(&input.project, &manifest)
            .await?;
        self.contexts
            .write_context(&input.project, &input.agent_id, &template.content_md)
            .await?;

        self.events.publish(DomainEvent::AgentSynced {
            agent_id: input.agent_id,
            to: template.version,
        });

        Ok(SyncAgentWithTemplateOutput {
            synced: true,
            version: Some(template.version),
        })
    }
}
