use crate::component_detection::DetectedSingBox;
use crate::models::{DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT, DEFAULT_LOCAL_SINGBOX_SERVICE_NAME};
use serde::{Deserialize, Serialize};
use std::path::Path;

pub const WINSW_WRAPPER_FILE: &str = "ProxyWardenSingBox.exe";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SingBoxServiceAction {
    Start,
    Stop,
}

impl SingBoxServiceAction {
    pub fn action_name(self) -> &'static str {
        match self {
            SingBoxServiceAction::Start => "start",
            SingBoxServiceAction::Stop => "stop",
        }
    }

    pub fn label(self) -> &'static str {
        match self {
            SingBoxServiceAction::Start => "запустить",
            SingBoxServiceAction::Stop => "остановить",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SingBoxSetupStatus {
    pub ready: bool,
    pub missing_count: usize,
    pub items: Vec<SingBoxSetupItem>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SingBoxSetupItem {
    pub id: String,
    pub name: String,
    pub installed: bool,
    pub version: Option<String>,
    pub details: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceCommandOutput {
    pub success: bool,
    pub code: String,
    pub service_name: Option<String>,
    pub status: Option<String>,
    pub process_id: Option<u32>,
}

pub fn build_singbox_setup_status(detected: Option<&DetectedSingBox>) -> SingBoxSetupStatus {
    let install_root = detected
        .map(|singbox| singbox.install_dir.display().to_string())
        .unwrap_or_else(|| DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT.to_string());
    let binary_item = match detected {
        Some(singbox) if singbox.binary_exists => SingBoxSetupItem {
            id: "sing-box-binary".to_string(),
            name: "sing-box".to_string(),
            installed: true,
            version: Some("binary найден".to_string()),
            details: singbox.executable_path.display().to_string(),
        },
        _ => SingBoxSetupItem {
            id: "sing-box-binary".to_string(),
            name: "sing-box".to_string(),
            installed: false,
            version: None,
            details: format!(
                "Будет скачан из GitHub releases SagerNet/sing-box и установлен в {install_root}."
            ),
        },
    };
    let wrapper_item = match detected {
        Some(singbox) if singbox.wrapper_exists => SingBoxSetupItem {
            id: "winsw-wrapper".to_string(),
            name: "WinSW service wrapper".to_string(),
            installed: true,
            version: Some("wrapper найден".to_string()),
            details: singbox.wrapper_path.display().to_string(),
        },
        _ => SingBoxSetupItem {
            id: "winsw-wrapper".to_string(),
            name: "WinSW service wrapper".to_string(),
            installed: false,
            version: None,
            details: format!(
                "Будет скачан из GitHub releases winsw/winsw как {WINSW_WRAPPER_FILE}."
            ),
        },
    };
    let service_item = match detected {
        Some(singbox) if singbox.running => SingBoxSetupItem {
            id: "windows-service".to_string(),
            name: DEFAULT_LOCAL_SINGBOX_SERVICE_NAME.to_string(),
            installed: true,
            version: Some("служба запущена".to_string()),
            details: format!("Служба {}", singbox.service_name),
        },
        Some(singbox) => SingBoxSetupItem {
            id: "windows-service".to_string(),
            name: DEFAULT_LOCAL_SINGBOX_SERVICE_NAME.to_string(),
            installed: true,
            version: Some("служба остановлена".to_string()),
            details: format!("Служба {}", singbox.service_name),
        },
        None => SingBoxSetupItem {
            id: "windows-service".to_string(),
            name: DEFAULT_LOCAL_SINGBOX_SERVICE_NAME.to_string(),
            installed: false,
            version: None,
            details: "Будет создана Windows-служба Local sing-box.".to_string(),
        },
    };

    let items = vec![binary_item, wrapper_item, service_item];
    let missing_count = items.iter().filter(|item| !item.installed).count();

    SingBoxSetupStatus {
        ready: missing_count == 0,
        missing_count,
        items,
    }
}

pub fn parse_service_command_output(stdout: &[u8]) -> Option<ServiceCommandOutput> {
    let stdout = String::from_utf8_lossy(stdout);
    let payload = stdout
        .lines()
        .rev()
        .map(str::trim)
        .find(|line| line.starts_with('{') && line.ends_with('}'))?;

    serde_json::from_str(payload).ok()
}

pub fn ensure_safe_singbox_install_dir(path: &Path) -> Result<(), String> {
    let normalized = path
        .display()
        .to_string()
        .replace('/', "\\")
        .to_ascii_lowercase();
    let file_name = path
        .file_name()
        .and_then(|value| value.to_str())
        .unwrap_or_default()
        .to_ascii_lowercase();

    if file_name == "sing-box"
        && (normalized.contains("\\proxywarden\\") || normalized.contains("\\proxywarden\\"))
    {
        return Ok(());
    }

    Err(format!(
        "Отказываюсь рекурсивно удалять Local sing-box с небезопасным путем: {}",
        path.display()
    ))
}

pub fn service_control_script(
    action: SingBoxServiceAction,
    service_name: &str,
    config_source: Option<&Path>,
    config_target: Option<&Path>,
) -> String {
    let action_name = action.action_name();
    let escaped_service_name = escape_powershell_single(service_name);
    let escaped_config_source = config_source
        .map(|path| escape_powershell_single(&path.display().to_string()))
        .unwrap_or_default();
    let escaped_config_target = config_target
        .map(|path| escape_powershell_single(&path.display().to_string()))
        .unwrap_or_default();
    format!(
        r#"
$ErrorActionPreference = 'Stop'
$serviceName = '{escaped_service_name}'
$action = '{action_name}'
$configSource = '{escaped_config_source}'
$configTarget = '{escaped_config_target}'

function Get-ServiceProcessId([string]$name) {{
  $escapedName = $name.Replace("'", "''")
  $record = Get-CimInstance Win32_Service -Filter "Name='$escapedName'" -ErrorAction SilentlyContinue
  if ($null -eq $record) {{ return 0 }}
  return [int]$record.ProcessId
}}

function Get-ServiceStatus([string]$name) {{
  $current = Get-Service -Name $name -ErrorAction SilentlyContinue
  if ($null -eq $current) {{ return $null }}
  return $current.Status.ToString()
}}

function Write-ServiceResult([bool]$success, [string]$code, [string]$status, [int]$processId) {{
  [PSCustomObject]@{{
    success = $success
    code = $code
    serviceName = $serviceName
    status = $status
    processId = $processId
  }} | ConvertTo-Json -Compress
  exit 0
}}

function Sync-ServiceConfig {{
  if ($action -ne 'start' -or [string]::IsNullOrWhiteSpace($configSource)) {{ return }}
  if (-not (Test-Path -LiteralPath $configSource)) {{
    Write-ServiceResult $false 'config_source_missing' (Get-ServiceStatus $serviceName) (Get-ServiceProcessId $serviceName)
  }}
  if ([string]::IsNullOrWhiteSpace($configTarget)) {{ return }}

  try {{
    Copy-Item -LiteralPath $configSource -Destination $configTarget -Force -ErrorAction Stop
  }} catch {{
    Write-ServiceResult $false 'config_sync_failed' (Get-ServiceStatus $serviceName) (Get-ServiceProcessId $serviceName)
  }}
}}

$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($null -eq $service) {{
  Write-ServiceResult $false 'service_not_found' $null 0
}}

if ($action -eq 'start') {{
  Sync-ServiceConfig

  if ($service.Status -eq 'Running') {{
    Write-ServiceResult $true 'already_running' $service.Status.ToString() (Get-ServiceProcessId $serviceName)
  }}

  try {{
    Start-Service -Name $serviceName -ErrorAction Stop
    $service = Get-Service -Name $serviceName -ErrorAction Stop
    $service.WaitForStatus('Running', [TimeSpan]::FromSeconds(15))
  }} catch {{
    Write-ServiceResult $false 'start_failed' (Get-ServiceStatus $serviceName) (Get-ServiceProcessId $serviceName)
  }}

  Write-ServiceResult ($service.Status -eq 'Running') 'started' $service.Status.ToString() (Get-ServiceProcessId $serviceName)
}}

if ($service.Status -eq 'Stopped') {{
  Write-ServiceResult $true 'already_stopped' $service.Status.ToString() (Get-ServiceProcessId $serviceName)
}}

try {{
  Stop-Service -Name $serviceName -Force -ErrorAction Stop
  $service = Get-Service -Name $serviceName -ErrorAction Stop
  $service.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(15))
}} catch {{
  Write-ServiceResult $false 'stop_failed' (Get-ServiceStatus $serviceName) (Get-ServiceProcessId $serviceName)
}}

Write-ServiceResult ($service.Status -eq 'Stopped') 'stopped' $service.Status.ToString() (Get-ServiceProcessId $serviceName)
"#
    )
}

fn escape_powershell_single(value: &str) -> String {
    value.replace('\'', "''")
}
