use provctl_core::{ProcessStatus, ProvctlResult, ServiceDefinition};
use std::fs;
use std::path::PathBuf;
use std::process::Command;

use crate::backend::Backend;

/// Universal PID-file based service backend.
///
/// Works on any Unix-like system (Linux, macOS, BSD) by managing processes
/// using PID files. This is the fallback backend when systemd or launchd
/// are not available.
///
/// # How it works
///
/// 1. **Start**: Spawns process, writes PID to file
/// 2. **Stop**: Reads PID from file, sends SIGTERM
/// 3. **Status**: Reads PID, checks if process exists
/// 4. **Logs**: Reads from configured log file
///
/// # Examples
///
/// ```ignore
/// use provctl_backend::PidfileBackend;
///
/// let backend = PidfileBackend::new("/tmp");
/// // ... use with Backend trait ...
/// ```
#[derive(Debug, Clone)]
pub struct PidfileBackend {
    /// Directory where PID files are stored
    pid_dir: PathBuf,
    /// Directory where log files are stored
    log_dir: Option<PathBuf>,
}

impl PidfileBackend {
    /// Creates a new PID-file based backend.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let backend = PidfileBackend::new("/var/run");
    /// ```
    pub fn new(pid_dir: impl Into<PathBuf>) -> Self {
        Self {
            pid_dir: pid_dir.into(),
            log_dir: None,
        }
    }

    /// Sets the log directory for this backend.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let backend = PidfileBackend::new("/var/run")
    ///     .with_log_dir("/var/log/provctl");
    /// ```
    #[must_use]
    pub fn with_log_dir(mut self, log_dir: impl Into<PathBuf>) -> Self {
        self.log_dir = Some(log_dir.into());
        self
    }

    /// Gets the path to the PID file for a service.
    fn pid_file(&self, service_name: &str) -> PathBuf {
        self.pid_dir.join(format!("{}.pid", service_name))
    }

    /// Gets the path to the log file for a service.
    fn log_file(&self, service_name: &str) -> PathBuf {
        self.log_dir
            .as_ref()
            .map(|d| d.join(format!("{}.log", service_name)))
            .unwrap_or_else(|| PathBuf::from(format!("/tmp/{}.log", service_name)))
    }

    /// Reads the PID from file.
    fn read_pid_file(&self, service_name: &str) -> ProvctlResult<Option<u32>> {
        let path = self.pid_file(service_name);

        if !path.exists() {
            return Ok(None);
        }

        let content = fs::read_to_string(&path).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::PidFileError {
                    path: path.to_string_lossy().to_string(),
                    reason: e.to_string(),
                },
                "Reading PID file".to_string(),
            )
        })?;

        content.trim().parse::<u32>().map(Some).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::PidFileError {
                    path: path.to_string_lossy().to_string(),
                    reason: format!("Invalid PID: {}", e),
                },
                "Parsing PID file".to_string(),
            )
        })
    }

    /// Writes the PID to file.
    fn write_pid_file(&self, service_name: &str, pid: u32) -> ProvctlResult<()> {
        let path = self.pid_file(service_name);

        fs::create_dir_all(&self.pid_dir).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::PidFileError {
                    path: self.pid_dir.to_string_lossy().to_string(),
                    reason: e.to_string(),
                },
                "Creating PID directory".to_string(),
            )
        })?;

        fs::write(&path, pid.to_string()).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::PidFileError {
                    path: path.to_string_lossy().to_string(),
                    reason: e.to_string(),
                },
                "Writing PID file".to_string(),
            )
        })
    }

    /// Removes the PID file.
    fn remove_pid_file(&self, service_name: &str) -> ProvctlResult<()> {
        let path = self.pid_file(service_name);

        if path.exists() {
            fs::remove_file(&path).map_err(|e| {
                provctl_core::ProvctlError::new(
                    provctl_core::ProvctlErrorKind::PidFileError {
                        path: path.to_string_lossy().to_string(),
                        reason: e.to_string(),
                    },
                    "Removing PID file".to_string(),
                )
            })?;
        }

        Ok(())
    }

    /// Checks if a process with the given PID exists.
    fn process_exists(&self, pid: u32) -> bool {
        // Try to send signal 0 (no-op) to check if process exists
        let status = Command::new("kill").arg("-0").arg(pid.to_string()).status();

        matches!(status, Ok(s) if s.success())
    }
}

#[async_trait::async_trait]
impl Backend for PidfileBackend {
    async fn start(&self, service: &ServiceDefinition) -> ProvctlResult<u32> {
        // Check if already running
        if let Some(existing_pid) = self.read_pid_file(service.name.as_str())? {
            if self.process_exists(existing_pid) {
                return Err(provctl_core::ProvctlError::new(
                    provctl_core::ProvctlErrorKind::AlreadyRunning {
                        service: service.name.to_string(),
                        pid: existing_pid,
                    },
                    "Starting service".to_string(),
                ));
            }
        }

        // Spawn the process
        let mut cmd = Command::new(&service.binary);

        if let Some(ref dir) = service.working_dir {
            cmd.current_dir(dir);
        }

        cmd.args(&service.args);

        for (key, value) in &service.env_vars {
            cmd.env(key, value);
        }

        let child = cmd.spawn().map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::SpawnError {
                    service: service.name.to_string(),
                    reason: e.to_string(),
                },
                "Spawning process".to_string(),
            )
        })?;

        let pid = child.id();

        // Write PID file
        self.write_pid_file(service.name.as_str(), pid)?;

        log::info!("Service started: {} (PID: {})", service.name.as_str(), pid);

        Ok(pid)
    }

    async fn stop(&self, service_name: &str) -> ProvctlResult<()> {
        let pid = self.read_pid_file(service_name)?.ok_or_else(|| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::NotRunning(service_name.to_string()),
                "Stopping service".to_string(),
            )
        })?;

        // Send SIGTERM
        let status = Command::new("kill")
            .arg("-15") // SIGTERM
            .arg(pid.to_string())
            .status()
            .map_err(|e| {
                provctl_core::ProvctlError::new(
                    provctl_core::ProvctlErrorKind::SpawnError {
                        service: service_name.to_string(),
                        reason: format!("kill failed: {}", e),
                    },
                    "Stopping service".to_string(),
                )
            })?;

        if !status.success() {
            return Err(provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::SpawnError {
                    service: service_name.to_string(),
                    reason: format!("kill returned code: {}", status.code().unwrap_or(-1)),
                },
                "Stopping service".to_string(),
            ));
        }

        // Clean up PID file
        self.remove_pid_file(service_name)?;

        log::info!("Service stopped: {} (PID: {})", service_name, pid);

        Ok(())
    }

    async fn status(&self, service_name: &str) -> ProvctlResult<ProcessStatus> {
        match self.read_pid_file(service_name)? {
            Some(pid) if self.process_exists(pid) => Ok(ProcessStatus::Running { pid }),
            Some(_pid) => {
                // PID file exists but process doesn't
                self.remove_pid_file(service_name).ok();
                Ok(ProcessStatus::NotRunning)
            }
            None => Ok(ProcessStatus::NotRunning),
        }
    }

    async fn logs(&self, service_name: &str, lines: usize) -> ProvctlResult<Vec<String>> {
        let path = self.log_file(service_name);

        if !path.exists() {
            return Ok(vec![]);
        }

        let content = fs::read_to_string(&path).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::Other(format!("Failed to read logs: {}", e)),
                "Reading log file".to_string(),
            )
        })?;

        let all_lines: Vec<&str> = content.lines().collect();

        let start = if all_lines.len() > lines {
            all_lines.len() - lines
        } else {
            0
        };

        Ok(all_lines[start..].iter().map(|s| s.to_string()).collect())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pidfile_creation() {
        let tmpdir = tempfile::TempDir::new().unwrap();
        let backend = PidfileBackend::new(tmpdir.path());
        assert_eq!(backend.pid_dir, tmpdir.path());
    }

    #[test]
    fn test_pid_file_path() {
        let tmpdir = tempfile::TempDir::new().unwrap();
        let backend = PidfileBackend::new(tmpdir.path());
        let path = backend.pid_file("api");
        assert!(path.to_string_lossy().contains("api.pid"));
    }

    #[test]
    fn test_write_and_read_pid() {
        let tmpdir = tempfile::TempDir::new().unwrap();
        let backend = PidfileBackend::new(tmpdir.path());

        backend.write_pid_file("test", 1234).unwrap();
        let pid = backend.read_pid_file("test").unwrap();
        assert_eq!(pid, Some(1234));
    }
}
