//! `MetaEventsBackend` impl for `JsonlBackend`.

use async_trait::async_trait;
use chrono::Utc;
use uuid::Uuid;

use crate::backend::MetaEventsBackend;
use crate::backends::jsonl::backend::JsonlBackend;
use crate::error::{ArchivistError, Result};
use crate::storage::{append_ndjson, read_json, read_ndjson, write_json};
use crate::types::{
    MetaEventRecord, SessionCompleteness, SessionKind, SessionMetadata,
};

#[async_trait]
impl MetaEventsBackend for JsonlBackend {
    async fn append_meta_events(
        &self,
        scroll_id: Uuid,
        events: Vec<MetaEventRecord>,
    ) -> Result<()> {
        // Ensure session directory exists
        self.paths.ensure_dirs(scroll_id).await?;

        // Append each event to events.jsonl
        let events_path = self.paths.events_path(scroll_id);
        for event in &events {
            append_ndjson(&events_path, event).await?;
        }

        // Update session.json timestamp
        let session_json_path = self.paths.session_json(scroll_id);
        let now = Utc::now();

        let session_metadata = match read_json::<SessionMetadata>(&session_json_path).await {
            Ok(mut metadata) => {
                metadata.updated_at = now;
                metadata
            }
            Err(_) => {
                // session.json doesn't exist, this shouldn't happen for meta sessions
                // but we'll handle it gracefully
                tracing::warn!(
                    scroll_id = %scroll_id,
                    "session.json missing when appending meta events, creating minimal metadata"
                );

                SessionMetadata {
                    version: 1,
                    scroll_id,
                    created_at: now,
                    updated_at: now,
                    title: None,
                    connector_uid: scroll_id, // Use scroll_id as placeholder
                    native_session_id: None,
                    agent_id: None,
                    parent_scroll_id: None,
                    continuation: None,
                    tags: Vec::new(),
                    metadata: serde_json::json!({}),
                    no_update: false,
                    kind: SessionKind::AcpConnection,
                    acp_client_id: None,
                    is_connected: None,
                    current_session_id: None,
                    models: None,
                    modes: None,
                    config_options: None,
                    completeness: SessionCompleteness::default(),
                    matrix_room_id: None,
                    matrix_sharing_active: false,
                    matrix_shared_at: None,
                    is_subagent: false,
                    subagent_type: None,
                    spawning_tool_use_id: None,
                }
            }
        };

        write_json(&session_json_path, &session_metadata).await?;

        Ok(())
    }

    async fn get_meta_events(&self, scroll_id: Uuid) -> Result<Vec<MetaEventRecord>> {
        let events_path = self.paths.events_path(scroll_id);

        // Read events from events.jsonl
        let mut events: Vec<MetaEventRecord> = read_ndjson(&events_path)
            .await
            .unwrap_or_else(|_| Vec::new());

        // Sort by timestamp then event_id for stable ordering
        events.sort_by(|a, b| {
            a.ts.cmp(&b.ts).then_with(|| a.event_id.cmp(&b.event_id))
        });

        Ok(events)
    }

    async fn update_meta_session_status(
        &self,
        scroll_id: Uuid,
        is_connected: bool,
        current_session_id: Option<Uuid>,
    ) -> Result<()> {
        // Load existing session metadata
        let session_json_path = self.paths.session_json(scroll_id);

        let mut session_metadata: SessionMetadata = match read_json(&session_json_path).await {
            Ok(metadata) => metadata,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                return Err(ArchivistError::SessionUnknown(scroll_id));
            }
            Err(e) => return Err(e.into()),
        };

        // Update connection status fields
        session_metadata.is_connected = Some(is_connected);
        session_metadata.current_session_id = current_session_id;
        session_metadata.updated_at = Utc::now();

        // Write updated metadata back to disk
        write_json(&session_json_path, &session_metadata).await?;

        tracing::info!(
            scroll_id = %scroll_id,
            is_connected = %is_connected,
            current_session_id = ?current_session_id,
            "Updated meta session status"
        );

        Ok(())
    }

    async fn list_meta_sessions(&self) -> Result<Vec<SessionMetadata>> {
        // Scan .contexts/ directory for all session.json files
        let contexts_dir = self.paths.root().join(".contexts");

        if !contexts_dir.exists() {
            return Ok(Vec::new());
        }

        let mut meta_sessions = Vec::new();

        // Read all session directories
        let mut entries = tokio::fs::read_dir(&contexts_dir).await?;

        while let Some(entry) = entries.next_entry().await? {
            if !entry.file_type().await?.is_dir() {
                continue;
            }

            let session_json_path = entry.path().join("session.json");

            // Try to read session.json
            match read_json::<SessionMetadata>(&session_json_path).await {
                Ok(metadata) => {
                    // Filter to only AcpConnection sessions
                    if metadata.kind == SessionKind::AcpConnection {
                        meta_sessions.push(metadata);
                    }
                }
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                    // Skip missing session files
                    continue;
                }
                Err(e) => {
                    tracing::warn!(
                        path = ?session_json_path,
                        error = %e,
                        "Failed to read session.json while listing meta sessions"
                    );
                    continue;
                }
            }
        }

        // Sort by updated_at descending (newest first)
        meta_sessions.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));

        Ok(meta_sessions)
    }

    async fn find_meta_session_by_client(
        &self,
        client_id: &str,
    ) -> Result<Option<SessionMetadata>> {
        // Use list_meta_sessions and filter by acp_client_id
        let meta_sessions = self.list_meta_sessions().await?;

        let result = meta_sessions
            .into_iter()
            .find(|session| {
                session.acp_client_id.as_deref() == Some(client_id)
            });

        Ok(result)
    }
}
