use crate::models::ComponentId;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum HelperAction {
    #[serde(rename = "install-control-app")]
    InstallControlApp,
    #[serde(rename = "install-proxyfier")]
    InstallProxyfier,
    #[serde(rename = "install-singbox")]
    InstallSingbox,
    #[serde(rename = "proxyfier.apply")]
    ProxyfierApply,
    #[serde(rename = "service.status")]
    ServiceStatus,
    #[serde(rename = "service.start")]
    ServiceStart,
    #[serde(rename = "service.stop")]
    ServiceStop,
    #[serde(rename = "service.restart")]
    ServiceRestart,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HelperRequest {
    pub action: HelperAction,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub component: Option<ComponentId>,
    #[serde(default)]
    pub payload: Value,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HelperResponse {
    pub success: bool,
    pub action: HelperAction,
    pub changed: bool,
    pub message: String,
    #[serde(default)]
    pub details: Value,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HelperCommandSpec {
    pub program: PathBuf,
    pub args: Vec<String>,
    pub stdin: String,
    pub requires_elevation: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HelperCommandOutput {
    pub status_code: i32,
    pub stdout: String,
    pub stderr: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HelperError {
    pub code: String,
    pub message: String,
}

impl HelperError {
    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            code: code.into(),
            message: message.into(),
        }
    }
}

pub trait HelperCommandRunner {
    fn run(&self, spec: &HelperCommandSpec) -> Result<HelperCommandOutput, HelperError>;
}

#[derive(Debug, Clone)]
pub struct StructuredHelper<R> {
    helper_program: PathBuf,
    runner: R,
}

impl<R> StructuredHelper<R>
where
    R: HelperCommandRunner,
{
    pub fn new(helper_program: impl Into<PathBuf>, runner: R) -> Self {
        Self {
            helper_program: helper_program.into(),
            runner,
        }
    }

    pub fn runner(&self) -> &R {
        &self.runner
    }

    pub fn execute(&self, request: &HelperRequest) -> Result<HelperResponse, HelperError> {
        let stdin = serde_json::to_string(request)
            .map_err(|error| HelperError::new("helper_request_encode", error.to_string()))?;
        let spec = HelperCommandSpec {
            program: self.helper_program.clone(),
            args: vec!["--json".to_string()],
            stdin,
            requires_elevation: helper_action_requires_elevation(&request.action),
        };
        let output = self.runner.run(&spec)?;

        if output.status_code != 0 {
            return Err(HelperError::new(
                "helper_exit",
                format!(
                    "Помощник завершился с кодом {}: {}",
                    output.status_code, output.stderr
                ),
            ));
        }

        parse_helper_response(&output.stdout)
    }
}

pub fn parse_helper_response(stdout: &str) -> Result<HelperResponse, HelperError> {
    serde_json::from_str(stdout).map_err(|error| {
        HelperError::new(
            "helper_response_decode",
            format!("Помощник вернул не JSON или некорректный JSON: {error}"),
        )
    })
}

pub fn install_request(component: ComponentId) -> HelperRequest {
    let action = match component {
        ComponentId::ControlApp => HelperAction::InstallControlApp,
        ComponentId::Proxyfier => HelperAction::InstallProxyfier,
        ComponentId::Singbox => HelperAction::InstallSingbox,
    };

    HelperRequest {
        action,
        component: Some(component),
        payload: json!({}),
    }
}

pub fn service_request(component: ComponentId, action: HelperAction) -> HelperRequest {
    HelperRequest {
        action,
        component: Some(component),
        payload: json!({}),
    }
}

pub fn proxifyre_apply_request(
    config_path: impl AsRef<Path>,
    service_name: impl Into<String>,
) -> HelperRequest {
    HelperRequest {
        action: HelperAction::ProxyfierApply,
        component: Some(ComponentId::Proxyfier),
        payload: json!({
            "configPath": config_path.as_ref().display().to_string(),
            "serviceName": service_name.into(),
        }),
    }
}

pub fn helper_action_requires_elevation(action: &HelperAction) -> bool {
    matches!(
        action,
        HelperAction::InstallControlApp
            | HelperAction::InstallProxyfier
            | HelperAction::InstallSingbox
            | HelperAction::ProxyfierApply
            | HelperAction::ServiceStart
            | HelperAction::ServiceStop
            | HelperAction::ServiceRestart
    )
}
