// SPDX-License-Identifier: Apache-2.0
//! Session management commands.

use anyhow::Result;
use objects::object::{Session, SessionSegment};
use repo::SessionManager;
use serde::Serialize;

use super::{
    advice::RecoveryAdvice,
    git_overlay_health::{RepositoryVerificationState, build_repository_verification_state},
};
use crate::cli::{Cli, should_output_json};

#[derive(Serialize)]
struct SessionOutput {
    id: String,
    principal: String,
    created_at: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    ended_at: Option<String>,
    active: bool,
    segments: Vec<SegmentOutput>,
}

#[derive(Serialize)]
struct SegmentOutput {
    id: String,
    provider: String,
    model: String,
    started_at: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    policy_id: Option<String>,
}

#[derive(Serialize)]
struct SessionEnvelope {
    session: SessionOutput,
    #[allow(dead_code)]
    #[serde(skip_serializing)]
    #[serde(rename = "verification")]
    trust: RepositoryVerificationState,
}

#[derive(Serialize)]
struct SegmentEnvelope {
    segment: SegmentOutput,
    #[allow(dead_code)]
    #[serde(skip_serializing)]
    #[serde(rename = "verification")]
    trust: RepositoryVerificationState,
}

#[derive(Serialize)]
struct SessionListOutput {
    sessions: Vec<SessionOutput>,
    active_only: bool,
    #[serde(rename = "verification")]
    trust: RepositoryVerificationState,
}

impl From<&Session> for SessionOutput {
    fn from(session: &Session) -> Self {
        Self {
            id: session.id.clone(),
            principal: session.principal.to_string(),
            created_at: session.created_at.to_rfc3339(),
            ended_at: session.ended_at.map(|t| t.to_rfc3339()),
            active: session.is_active(),
            segments: session.segments.iter().map(SegmentOutput::from).collect(),
        }
    }
}

impl From<&SessionSegment> for SegmentOutput {
    fn from(segment: &SessionSegment) -> Self {
        Self {
            id: segment.id.clone(),
            provider: segment.provider.clone(),
            model: segment.model.clone(),
            started_at: segment.started_at.to_rfc3339(),
            policy_id: segment.policy_id.clone(),
        }
    }
}

pub async fn cmd_session_start(
    cli: &Cli,
    provider: String,
    model: String,
    policy: Option<String>,
) -> Result<()> {
    let repo = cli.open_repo()?;
    let mut manager = SessionManager::new(repo.root());
    let principal = repo.get_principal()?;

    let session = manager.start_session(principal, provider, model, policy)?;

    if should_output_json(cli, None) {
        let output = SessionEnvelope {
            session: SessionOutput::from(&session),
            trust: build_repository_verification_state(&repo),
        };
        println!("{}", serde_json::to_string(&output)?);
    } else {
        println!("Session: {}", session.id);
        let segment = session.current_segment().unwrap();
        println!("Segment: {}", segment.id);
    }

    Ok(())
}

pub async fn cmd_session_segment(
    cli: &Cli,
    provider: String,
    model: String,
    policy: Option<String>,
) -> Result<()> {
    let repo = cli.open_repo()?;
    let mut manager = SessionManager::new(repo.root());

    let current_id = manager.get_current_session_id()?.ok_or_else(|| {
        anyhow::anyhow!(RecoveryAdvice::no_current_session(
            "session segment",
            None,
            "heddle session start",
        ))
    })?;

    let segment = manager.add_segment(&current_id, provider, model, policy)?;

    if should_output_json(cli, None) {
        let output = SegmentEnvelope {
            segment: SegmentOutput::from(&segment),
            trust: build_repository_verification_state(&repo),
        };
        println!("{}", serde_json::to_string(&output)?);
    } else {
        println!("Segment: {}", segment.id);
    }

    Ok(())
}

pub async fn cmd_session_end(cli: &Cli, session_id: Option<String>) -> Result<()> {
    let repo = cli.open_repo()?;
    let mut manager = SessionManager::new(repo.root());

    let session = manager.end_session(session_id.as_deref())?;

    if should_output_json(cli, None) {
        let output = SessionEnvelope {
            session: SessionOutput::from(&session),
            trust: build_repository_verification_state(&repo),
        };
        println!("{}", serde_json::to_string(&output)?);
    } else {
        println!("Session ended: {}", session.id);
    }

    Ok(())
}

pub async fn cmd_session_show(cli: &Cli, session_id: Option<String>) -> Result<()> {
    let repo = cli.open_repo()?;
    let manager = SessionManager::new(repo.root());

    let id = match session_id {
        Some(id) => id,
        None => manager.get_current_session_id()?.ok_or_else(|| {
            anyhow::anyhow!(RecoveryAdvice::no_current_session(
                "session show",
                Some("<SESSION_ID>"),
                "heddle session start",
            ))
        })?,
    };

    let session = manager
        .get_session(&id)?
        .ok_or_else(|| anyhow::anyhow!("Session not found: {}", id))?;

    if should_output_json(cli, None) {
        let output = SessionEnvelope {
            session: SessionOutput::from(&session),
            trust: build_repository_verification_state(&repo),
        };
        println!("{}", serde_json::to_string(&output)?);
    } else {
        println!("Session: {}", session.id);
        println!("Principal: {}", session.principal);
        println!(
            "Created: {}",
            session.created_at.format("%Y-%m-%d %H:%M:%S UTC")
        );
        if let Some(ended) = session.ended_at {
            println!("Ended: {}", ended.format("%Y-%m-%d %H:%M:%S UTC"));
        }
        println!(
            "Status: {}",
            if session.is_active() {
                "active"
            } else {
                "ended"
            }
        );
        println!();
        println!("Segments:");
        for (i, seg) in session.segments.iter().enumerate() {
            println!("  {}. {} ({}/{})", i + 1, seg.id, seg.provider, seg.model);
            if let Some(ref policy) = seg.policy_id {
                println!("     Policy: {}", policy);
            }
        }
    }

    Ok(())
}

pub async fn cmd_session_list(cli: &Cli, active_only: bool) -> Result<()> {
    let repo = cli.open_repo()?;
    let manager = SessionManager::new(repo.root());

    let sessions = manager.list_sessions(active_only)?;

    if should_output_json(cli, None) {
        let output = SessionListOutput {
            sessions: sessions.iter().map(SessionOutput::from).collect(),
            active_only,
            trust: build_repository_verification_state(&repo),
        };
        println!("{}", serde_json::to_string(&output)?);
    } else {
        if sessions.is_empty() {
            println!("No sessions found.");
            return Ok(());
        }

        println!("Sessions:");
        for session in sessions {
            let status = if session.is_active() {
                "active"
            } else {
                "ended"
            };
            println!(
                "  {} [{}] - {} segments - {}",
                session.id,
                status,
                session.segments.len(),
                session.created_at.format("%Y-%m-%d %H:%M")
            );
        }
    }

    Ok(())
}
