#![allow(dead_code)]

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Request {
    DaemonStatus,
    DaemonStop,
    Spawn {
        spec: RunSpec,
    },
    StopProcess {
        selector: ProcessSelector,
        force: bool,
        /// Time SIGTERM gets before escalating to SIGKILL; None = default.
        grace_ms: Option<u64>,
    },
    SetTimeout {
        selector: ProcessSelector,
        timeout_ms: Option<u64>,
    },
    WaitProcess {
        selector: ProcessSelector,
    },
    RestartProcess {
        selector: ProcessSelector,
    },
    Resources {
        selector: ProcessSelector,
    },
    Ports {
        selector: ProcessSelector,
    },
    ListProcesses {
        /// When false, only running and lost-but-alive processes.
        all: bool,
    },
    ShowProcess {
        selector: ProcessSelector,
    },
    ReadLogs {
        /// None reads across every process.
        selector: Option<ProcessSelector>,
        stream: OutputStream,
        after_id: Option<i64>,
        since_ms: Option<i64>,
        until_ms: Option<i64>,
        /// Overrides `after_id`: read only the last N lines (0 = none).
        tail_lines: Option<u64>,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProcessSelector {
    Id(i64),
    Name(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunSpec {
    pub name: Option<String>,
    pub replace: bool,
    pub timeout_ms: Option<u64>,
    pub command: Vec<String>,
    pub cwd: String,
    pub inherit_env: bool,
    pub env_files: Vec<String>,
    pub env: Vec<EnvVar>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvVar {
    pub key: String,
    pub value: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessEnvSummary {
    pub inherit_env: bool,
    pub env_files: Vec<String>,
    pub env_keys: Vec<String>,
}

impl Request {
    pub fn name(&self) -> &'static str {
        match self {
            Self::DaemonStatus => "daemon status",
            Self::DaemonStop => "daemon stop",
            Self::Spawn { .. } => "run",
            Self::StopProcess { .. } => "stop",
            Self::SetTimeout { .. } => "timeout",
            Self::WaitProcess { .. } => "wait",
            Self::RestartProcess { .. } => "restart",
            Self::Resources { .. } => "resources",
            Self::Ports { .. } => "ports",
            Self::ListProcesses { .. } => "ps",
            Self::ShowProcess { .. } => "show",
            Self::ReadLogs { .. } => "logs",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Response {
    DaemonStatus {
        pid: u32,
        socket: String,
        database: String,
    },
    DaemonStopping,
    Spawned(ProcessSummary),
    StoppedProcess {
        id: i64,
        signal: StopSignal,
    },
    TimeoutUpdated {
        id: i64,
        timeout_ms: Option<u64>,
    },
    WaitedProcess(ProcessDetails),
    ProcessList(Vec<ProcessSummary>),
    ProcessDetails(ProcessDetails),
    ResourceSnapshot(ResourceSnapshot),
    PortList(PortList),
    Output {
        chunks: Vec<OutputChunk>,
        /// Next poll's `after_id`; valid even when `chunks` is empty.
        resume_after_id: i64,
    },
    Error {
        message: String,
    },
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum StopSignal {
    Term,
    Kill,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum OutputStream {
    All,
    Stdout,
    Stderr,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputChunk {
    pub id: i64,
    pub process_id: i64,
    pub stream: OutputStream,
    pub data: Vec<u8>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProcessStatus {
    Running,
    Exited,
    Failed,
    Killed,
    TimedOut,
    Lost,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessSummary {
    pub id: i64,
    pub name: Option<String>,
    pub status: ProcessStatus,
    pub pid: Option<u32>,
    pub pgid: Option<u32>,
    pub exit_code: Option<i32>,
    pub error_message: Option<String>,
    pub timeout_ms: Option<u64>,
    pub timeout_at_ms: Option<i64>,
    pub ports_unavailable: bool,
    pub ports: Vec<u16>,
    pub command: Vec<String>,
    pub env: ProcessEnvSummary,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessDetails {
    pub id: i64,
    pub name: Option<String>,
    pub status: ProcessStatus,
    pub pid: Option<u32>,
    pub pgid: Option<u32>,
    pub exit_code: Option<i32>,
    pub error_message: Option<String>,
    pub timeout_ms: Option<u64>,
    pub timeout_at_ms: Option<i64>,
    pub command: Vec<String>,
    pub cwd: String,
    pub env: ProcessEnvSummary,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceSnapshot {
    pub process_id: i64,
    pub name: Option<String>,
    pub status: ProcessStatus,
    pub pid: Option<u32>,
    pub pgid: Option<u32>,
    pub process_count: usize,
    pub total_memory_bytes: u64,
    pub total_cpu_percent: f32,
    pub processes: Vec<ResourceProcess>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceProcess {
    pub pid: u32,
    pub parent_pid: Option<u32>,
    pub name: String,
    pub memory_bytes: u64,
    pub cpu_percent: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortList {
    pub process_id: i64,
    pub name: Option<String>,
    pub status: ProcessStatus,
    pub unavailable: bool,
    pub ports: Vec<PortInfo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortInfo {
    pub protocol: String,
    pub state: String,
    pub local_addr: String,
    pub local_port: u16,
    pub pids: Vec<u32>,
}

impl std::fmt::Display for ProcessStatus {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let status = match self {
            Self::Running => "running",
            Self::Exited => "exited",
            Self::Failed => "failed",
            Self::Killed => "killed",
            Self::TimedOut => "timed_out",
            Self::Lost => "lost",
        };

        formatter.write_str(status)
    }
}

impl std::fmt::Display for StopSignal {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let signal = match self {
            Self::Term => "TERM",
            Self::Kill => "KILL",
        };

        formatter.write_str(signal)
    }
}
