use super::*;

impl LocalAgentHandle {
    // ── Schedule management API (Phase 7) ─────────────────────────────────

    /// Create a recurring task and schedule for a session.
    ///
    /// Verifies the scheduler is available, then creates the recurring task row
    /// and cleans it up again if schedule registration fails.
    ///
    /// Returns the schedule public ID, or an error if the scheduler is not running
    /// or the session cannot be found.
    pub async fn create_scheduled_task(
        &self,
        session_public_id: &str,
        prompt: &str,
        trigger: crate::session::domain_schedule::ScheduleTrigger,
        max_steps: Option<u32>,
        max_cost_usd: Option<f64>,
        max_runs: Option<u32>,
    ) -> Result<String, agent_client_protocol::Error> {
        use crate::session::domain::{Task, TaskKind, TaskStatus};
        use crate::session::domain_schedule::ScheduleExecutionLimits;

        // Look up session to get the internal row ID required by FK constraints.
        let store = self.config.provider.history_store();
        let session = store
            .get_session(session_public_id)
            .await
            .map_err(|e| agent_client_protocol::Error::internal_error().data(e.to_string()))?
            .ok_or_else(|| {
                agent_client_protocol::Error::invalid_params()
                    .data(format!("Session not found: {session_public_id}"))
            })?;

        let scheduler = self
            .get_or_start_scheduler()
            .await
            .ok_or_else(Self::scheduler_unavailable_error)?;

        // Create the task row so the schedule FK is satisfied.
        let now = time::OffsetDateTime::now_utc();
        let task = Task {
            id: 0,                    // populated by create_task
            public_id: String::new(), // generated by create_task
            session_id: session.id,
            kind: TaskKind::Recurring,
            status: TaskStatus::Active,
            expected_deliverable: Some(prompt.to_string()),
            acceptance_criteria: None,
            created_at: now,
            updated_at: now,
        };
        let task = store
            .create_task(task)
            .await
            .map_err(|e| agent_client_protocol::Error::internal_error().data(e.to_string()))?;

        // Build the schedule with valid internal IDs.
        let mut schedule = crate::session::domain_schedule::Schedule::new(
            task.public_id.clone(),
            session_public_id.to_string(),
            trigger,
        );
        schedule.task_id = task.id;
        schedule.session_id = session.id;

        // Apply user-provided limits.
        if max_runs.is_some() {
            schedule.config.max_runs = max_runs;
        }
        if max_steps.is_some() || max_cost_usd.is_some() {
            schedule.config.execution_limits = Some(ScheduleExecutionLimits {
                max_steps,
                max_cost_usd,
            });
        }

        let schedule_public_id = schedule.public_id.clone();

        match scheduler.add_schedule(schedule).await {
            Ok(()) => Ok(schedule_public_id),
            Err(e) => {
                let msg = e.to_string();
                if Self::is_actor_not_running_error(&msg) {
                    self.clear_scheduler_handle();
                }

                let cleanup_suffix = match store.delete_task(&task.public_id).await {
                    Ok(()) => String::new(),
                    Err(cleanup_err) => {
                        format!(
                            "; cleanup failed for task {}: {}",
                            task.public_id, cleanup_err
                        )
                    }
                };

                Err(agent_client_protocol::Error::internal_error()
                    .data(format!("{msg}{cleanup_suffix}")))
            }
        }
    }

    /// Trigger a schedule to fire immediately.
    pub async fn trigger_schedule_now(
        &self,
        schedule_public_id: &str,
    ) -> Result<(), agent_client_protocol::Error> {
        let scheduler = self
            .get_or_start_scheduler()
            .await
            .ok_or_else(Self::scheduler_unavailable_error)?;

        match scheduler.trigger_now(schedule_public_id).await {
            Ok(()) => Ok(()),
            Err(e) => {
                let msg = e.to_string();
                if !Self::is_actor_not_running_error(&msg) {
                    return Err(agent_client_protocol::Error::internal_error().data(msg));
                }

                self.clear_scheduler_handle();
                let scheduler = self
                    .get_or_start_scheduler()
                    .await
                    .ok_or_else(Self::scheduler_unavailable_error)?;
                scheduler
                    .trigger_now(schedule_public_id)
                    .await
                    .map_err(|err| {
                        agent_client_protocol::Error::internal_error().data(err.to_string())
                    })
            }
        }
    }

    /// Pause a schedule.
    pub async fn pause_schedule(
        &self,
        schedule_public_id: &str,
    ) -> Result<(), agent_client_protocol::Error> {
        let scheduler = self
            .get_or_start_scheduler()
            .await
            .ok_or_else(Self::scheduler_unavailable_error)?;

        match scheduler.pause_schedule(schedule_public_id).await {
            Ok(()) => Ok(()),
            Err(e) => {
                let msg = e.to_string();
                if !Self::is_actor_not_running_error(&msg) {
                    return Err(agent_client_protocol::Error::internal_error().data(msg));
                }

                self.clear_scheduler_handle();
                let scheduler = self
                    .get_or_start_scheduler()
                    .await
                    .ok_or_else(Self::scheduler_unavailable_error)?;
                scheduler
                    .pause_schedule(schedule_public_id)
                    .await
                    .map_err(|err| {
                        agent_client_protocol::Error::internal_error().data(err.to_string())
                    })
            }
        }
    }

    /// Resume a paused schedule.
    pub async fn resume_schedule(
        &self,
        schedule_public_id: &str,
    ) -> Result<(), agent_client_protocol::Error> {
        let scheduler = self
            .get_or_start_scheduler()
            .await
            .ok_or_else(Self::scheduler_unavailable_error)?;

        match scheduler.resume_schedule(schedule_public_id).await {
            Ok(()) => Ok(()),
            Err(e) => {
                let msg = e.to_string();
                if !Self::is_actor_not_running_error(&msg) {
                    return Err(agent_client_protocol::Error::internal_error().data(msg));
                }

                self.clear_scheduler_handle();
                let scheduler = self
                    .get_or_start_scheduler()
                    .await
                    .ok_or_else(Self::scheduler_unavailable_error)?;
                scheduler
                    .resume_schedule(schedule_public_id)
                    .await
                    .map_err(|err| {
                        agent_client_protocol::Error::internal_error().data(err.to_string())
                    })
            }
        }
    }

    /// Delete a schedule.
    pub async fn delete_schedule(
        &self,
        schedule_public_id: &str,
    ) -> Result<(), agent_client_protocol::Error> {
        let scheduler = self
            .get_or_start_scheduler()
            .await
            .ok_or_else(Self::scheduler_unavailable_error)?;

        match scheduler.remove_schedule(schedule_public_id).await {
            Ok(()) => Ok(()),
            Err(e) => {
                let msg = e.to_string();
                if !Self::is_actor_not_running_error(&msg) {
                    return Err(agent_client_protocol::Error::internal_error().data(msg));
                }

                self.clear_scheduler_handle();
                let scheduler = self
                    .get_or_start_scheduler()
                    .await
                    .ok_or_else(Self::scheduler_unavailable_error)?;
                scheduler
                    .remove_schedule(schedule_public_id)
                    .await
                    .map_err(|err| {
                        agent_client_protocol::Error::internal_error().data(err.to_string())
                    })
            }
        }
    }

    /// List schedules, optionally filtered by session.
    ///
    /// Returns an empty list if the scheduler is not running (rather than an
    /// error), since the frontend polls this on every session load and an
    /// absent scheduler simply means no schedules exist yet.
    pub async fn list_schedules(
        &self,
        session_public_id: Option<&str>,
    ) -> Result<Vec<crate::session::domain_schedule::Schedule>, agent_client_protocol::Error> {
        let scheduler = match self.get_or_start_scheduler().await {
            Some(s) => s,
            None => return Ok(vec![]),
        };

        match scheduler.list_schedules(session_public_id).await {
            Ok(schedules) => Ok(schedules),
            Err(e) => {
                let msg = e.to_string();
                if !Self::is_actor_not_running_error(&msg) {
                    return Err(agent_client_protocol::Error::internal_error().data(msg));
                }

                self.clear_scheduler_handle();
                let scheduler = match self.get_or_start_scheduler().await {
                    Some(s) => s,
                    None => return Ok(vec![]),
                };
                scheduler
                    .list_schedules(session_public_id)
                    .await
                    .or_else(|retry_err| {
                        if Self::is_actor_not_running_error(&retry_err.to_string()) {
                            Ok(vec![])
                        } else {
                            Err(retry_err)
                        }
                    })
                    .map_err(|retry_err| {
                        agent_client_protocol::Error::internal_error().data(retry_err.to_string())
                    })
            }
        }
    }

    /// Get a single schedule by public ID.
    ///
    /// Returns `None` if the scheduler is not running or the schedule does not exist.
    pub async fn get_schedule(
        &self,
        schedule_public_id: &str,
    ) -> Result<Option<crate::session::domain_schedule::Schedule>, agent_client_protocol::Error>
    {
        let scheduler = match self.get_or_start_scheduler().await {
            Some(s) => s,
            None => return Ok(None),
        };

        match scheduler.get_schedule(schedule_public_id).await {
            Ok(schedule) => Ok(schedule),
            Err(e) => {
                let msg = e.to_string();
                if !Self::is_actor_not_running_error(&msg) {
                    return Err(agent_client_protocol::Error::internal_error().data(msg));
                }

                self.clear_scheduler_handle();
                let scheduler = match self.get_or_start_scheduler().await {
                    Some(s) => s,
                    None => return Ok(None),
                };
                scheduler
                    .get_schedule(schedule_public_id)
                    .await
                    .or_else(|retry_err| {
                        if Self::is_actor_not_running_error(&retry_err.to_string()) {
                            Ok(None)
                        } else {
                            Err(retry_err)
                        }
                    })
                    .map_err(|retry_err| {
                        agent_client_protocol::Error::internal_error().data(retry_err.to_string())
                    })
            }
        }
    }

    /// Get scheduler metrics snapshot.
    pub async fn scheduler_metrics(&self) -> Option<crate::scheduler::SchedulerMetrics> {
        let scheduler = self.scheduler()?;
        Some(scheduler.metrics().await)
    }
}
