use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use eframe::egui;
use egui::{Color32, RichText};
use image::ImageFormat;
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};

#[cfg(any(target_os = "linux", target_os = "macos"))]
use std::collections::HashSet;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use std::process::Command;
#[cfg(target_os = "windows")]
use windows::core::PCWSTR;
#[cfg(target_os = "windows")]
use windows::Win32::Storage::FileSystem::{GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW};
#[cfg(target_os = "windows")]
use windows::Win32::System::WindowsProgramming::DRIVE_REMOVABLE;

fn main() -> eframe::Result<()> {
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_inner_size([720.0, 520.0])
            .with_min_inner_size([640.0, 420.0])
            .with_icon(Arc::new(app_icon())),
        ..Default::default()
    };

    eframe::run_native(
        "Digital Purchasing - USB Login",
        options,
        Box::new(|_cc| Ok(Box::new(MasterTokenClientApp::default()))),
    )
}

struct MasterTokenClientApp {
    server_url: String,
    return_url: String,
    devices: Vec<UsbTokenDevice>,
    selected_device_index: Option<usize>,
    status: String,
    busy: bool,
}

impl Default for MasterTokenClientApp {
    fn default() -> Self {
        let devices = detect_usb_devices();
        let status = if devices.is_empty() {
            "Подключите подготовленный USB-ключ и нажмите «Обновить».".to_owned()
        } else {
            format!("Обнаружено USB-устройств: {}.", devices.len())
        };

        Self {
            server_url: "https://digitalpurchasing.com".to_owned(),
            return_url: "/Dashboard".to_owned(),
            selected_device_index: devices.first().map(|_| 0),
            devices,
            status,
            busy: false,
        }
    }
}

impl eframe::App for MasterTokenClientApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default()
            .frame(egui::Frame::default().fill(Color32::from_rgb(246, 248, 252)))
            .show(ctx, |ui| {
                ui.add_space(12.0);

                egui::Frame::new()
                    .fill(Color32::from_rgb(24, 71, 140))
                    .corner_radius(14)
                    .inner_margin(egui::Margin::same(18))
                    .show(ui, |ui| {
                        ui.vertical(|ui| {
                            ui.label(
                                RichText::new("Digital Purchasing")
                                    .size(24.0)
                                    .color(Color32::WHITE)
                                    .strong(),
                            );
                            ui.add_space(4.0);
                            ui.label(
                                RichText::new("Клиент безопасного входа по USB-ключу")
                                    .size(15.0)
                                    .color(Color32::from_rgb(213, 229, 255)),
                            );
                        });
                    });

                ui.add_space(16.0);

                egui::Frame::new()
                    .fill(Color32::WHITE)
                    .corner_radius(14)
                    .stroke(egui::Stroke::new(1.0, Color32::from_rgb(220, 227, 239)))
                    .inner_margin(egui::Margin::same(18))
                    .show(ui, |ui| {
                        ui.heading("Подключение");
                        ui.add_space(8.0);
                        ui.label("Адрес сервера");
                        ui.text_edit_singleline(&mut self.server_url);
                        ui.add_space(8.0);
                        ui.label("URL возврата");
                        ui.text_edit_singleline(&mut self.return_url);
                        ui.add_space(8.0);
                        ui.label(
                            RichText::new("При необходимости укажите адрес вашего экземпляра Digital Purchasing.")
                                .small()
                                .color(Color32::from_rgb(99, 107, 126)),
                        );
                    });

                ui.add_space(14.0);

                egui::Frame::new()
                    .fill(Color32::WHITE)
                    .corner_radius(14)
                    .stroke(egui::Stroke::new(1.0, Color32::from_rgb(220, 227, 239)))
                    .inner_margin(egui::Margin::same(18))
                    .show(ui, |ui| {
                        ui.horizontal(|ui| {
                            ui.heading("USB-устройства");
                            if ui.button("Обновить").clicked() && !self.busy {
                                self.refresh_devices();
                            }
                        });

                        ui.add_space(10.0);

                        if self.devices.is_empty() {
                            ui.label("USB-ключ не найден. Подключите выданную флешку и нажмите «Обновить».");
                        } else {
                            for (index, device) in self.devices.iter().enumerate() {
                                let selected = self.selected_device_index == Some(index);
                                let title = format!(
                                    "{}  |  {}  |  {}",
                                    device.location, device.device_name, device.usb_serial_number
                                );
                                ui.radio_value(&mut self.selected_device_index, Some(index), title);
                                if let Some(email) = &device.email {
                                    ui.label(RichText::new(email).small().color(Color32::from_rgb(99, 107, 126)));
                                }
                                if let Some(company_name) = &device.company_name {
                                    ui.label(
                                        RichText::new(company_name)
                                            .small()
                                            .color(Color32::from_rgb(99, 107, 126)),
                                    );
                                }
                                ui.add_space(8.0);
                                if selected {
                                    ui.separator();
                                }
                            }
                        }
                    });

                ui.add_space(14.0);

                egui::Frame::new()
                    .fill(Color32::WHITE)
                    .corner_radius(14)
                    .stroke(egui::Stroke::new(1.0, Color32::from_rgb(220, 227, 239)))
                    .inner_margin(egui::Margin::same(18))
                    .show(ui, |ui| {
                        if ui
                            .add_enabled(!self.busy, egui::Button::new("Войти с USB-ключом"))
                            .clicked()
                        {
                            self.sign_in();
                        }

                        ui.add_space(10.0);
                        if !self.status.is_empty() {
                            ui.label(RichText::new(&self.status).color(Color32::from_rgb(68, 78, 96)));
                        }
                    });
            });
    }
}

impl MasterTokenClientApp {
    fn refresh_devices(&mut self) {
        self.status.clear();
        self.devices = detect_usb_devices();
        self.selected_device_index = self.devices.first().map(|_| 0);

        if self.devices.is_empty() {
            self.status = "USB-ключ не обнаружен.".to_owned();
        } else {
            self.status = format!("Обнаружено USB-устройств: {}.", self.devices.len());
        }
    }

    fn sign_in(&mut self) {
        let Some(index) = self.selected_device_index else {
            self.status = "Сначала выберите USB-устройство.".to_owned();
            return;
        };

        let Some(device) = self.devices.get(index) else {
            self.status = "Выбранное USB-устройство недоступно.".to_owned();
            return;
        };

        self.busy = true;
        self.status = "Выполняется запрос на вход...".to_owned();

        let request = DeviceLoginRequest {
            token: device.master_token.clone(),
            usb_serial_number: device.usb_serial_number.clone(),
            return_url: self.return_url.trim().to_owned(),
        };

        let url = format!("{}/auth/device-login/request", self.server_url.trim_end_matches('/'));
        let response = Client::new().post(url).json(&request).send();

        match response {
            Ok(response) if response.status().is_success() => match response.json::<DeviceLoginResponse>() {
                Ok(payload) => {
                    if let Some(login_url) = payload.login_url {
                        match open::that(login_url) {
                            Ok(_) => {
                                self.status = "Браузер открыт. Завершаем вход в систему.".to_owned();
                            }
                            Err(error) => {
                                self.status = format!("Ссылка для входа получена, но браузер не удалось открыть: {error}");
                            }
                        }
                    } else {
                        self.status = "Сервер не вернул ссылку для завершения входа.".to_owned();
                    }
                }
                Err(error) => {
                    self.status = format!("Не удалось обработать ответ сервера: {error}");
                }
            },
            Ok(response) => {
                self.status = format!("Сервер отклонил запрос на вход: HTTP {}.", response.status());
            }
            Err(error) => {
                self.status = format!("Не удалось связаться с сервером: {error}");
            }
        }

        self.busy = false;
    }
}

#[derive(Clone)]
struct UsbTokenDevice {
    location: String,
    device_name: String,
    master_token: String,
    usb_serial_number: String,
    email: Option<String>,
    company_name: Option<String>,
}

#[derive(Deserialize)]
struct UsbTokenFile {
    #[serde(rename = "deviceName")]
    device_name: Option<String>,
    #[serde(rename = "masterToken")]
    master_token: String,
    email: Option<String>,
    #[serde(rename = "companyName")]
    company_name: Option<String>,
}

#[derive(Serialize)]
struct DeviceLoginRequest {
    #[serde(rename = "token")]
    token: String,
    #[serde(rename = "usbSerialNumber")]
    usb_serial_number: String,
    #[serde(rename = "returnUrl")]
    return_url: String,
}

#[derive(Deserialize)]
struct DeviceLoginResponse {
    #[serde(rename = "loginUrl")]
    login_url: Option<String>,
}

#[cfg(target_os = "windows")]
fn detect_usb_devices() -> Vec<UsbTokenDevice> {
    let mut devices = Vec::new();
    let drives_mask = unsafe { GetLogicalDrives() };

    for offset in 0..26u32 {
        if drives_mask & (1 << offset) == 0 {
            continue;
        }

        let drive_root = format!("{}:\\", (b'A' + offset as u8) as char);
        let wide_drive = to_wide(&drive_root);

        let drive_type = unsafe { GetDriveTypeW(PCWSTR(wide_drive.as_ptr())) };
        if drive_type != DRIVE_REMOVABLE {
            continue;
        }

        let mount_path = Path::new(&drive_root);
        let Some(token_device) = build_usb_device(
            mount_path,
            drive_root.trim_end_matches('\\').to_owned(),
            || get_windows_volume_serial_number(&drive_root),
        ) else {
            continue;
        };

        devices.push(token_device);
    }

    devices
}

#[cfg(any(target_os = "linux", target_os = "macos"))]
fn detect_usb_devices() -> Vec<UsbTokenDevice> {
    let mut devices = Vec::new();
    let mut seen_mounts = HashSet::new();
    let mut mount_candidates = Vec::new();

    for (root, max_depth) in platform_mount_roots() {
        collect_mount_candidates(Path::new(root), *max_depth, 0, &mut mount_candidates, &mut seen_mounts);
    }

    for mount_path in mount_candidates {
        let location = mount_path
            .file_name()
            .and_then(|value| value.to_str())
            .map(|value| value.to_owned())
            .unwrap_or_else(|| mount_path.display().to_string());

        let Some(token_device) = build_usb_device(&mount_path, location, || get_unix_usb_serial(&mount_path)) else {
            continue;
        };

        devices.push(token_device);
    }

    devices
}

fn build_usb_device<F>(mount_path: &Path, location: String, serial_factory: F) -> Option<UsbTokenDevice>
where
    F: FnOnce() -> Option<String>,
{
    let token_file_path = find_token_file(mount_path)?;
    let contents = fs::read_to_string(&token_file_path).ok()?;
    let token_file = serde_json::from_str::<UsbTokenFile>(&contents).ok()?;
    let serial_number = sanitize_serial(&serial_factory()?);

    Some(UsbTokenDevice {
        location,
        device_name: token_file
            .device_name
            .unwrap_or_else(|| "USB Key".to_owned()),
        master_token: token_file.master_token,
        usb_serial_number: serial_number,
        email: token_file.email,
        company_name: token_file.company_name,
    })
}

fn find_token_file(root: &Path) -> Option<PathBuf> {
    let entries = fs::read_dir(root).ok()?;
    for entry in entries.flatten() {
        let path = entry.path();
        if !path.is_file() {
            continue;
        }

        let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else {
            continue;
        };

        if file_name.eq_ignore_ascii_case("dp-master-token.json")
            || (file_name.starts_with("dp-master-token-") && file_name.ends_with(".json"))
        {
            return Some(path);
        }
    }

    None
}

#[cfg(any(target_os = "linux", target_os = "macos"))]
fn collect_mount_candidates(
    root: &Path,
    max_depth: usize,
    current_depth: usize,
    mounts: &mut Vec<PathBuf>,
    seen_mounts: &mut HashSet<PathBuf>,
) {
    if !root.exists() || !root.is_dir() {
        return;
    }

    let root_path = root.to_path_buf();
    if seen_mounts.insert(root_path.clone()) {
        mounts.push(root_path);
    }

    if current_depth >= max_depth {
        return;
    }

    let Ok(entries) = fs::read_dir(root) else {
        return;
    };

    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            collect_mount_candidates(&path, max_depth, current_depth + 1, mounts, seen_mounts);
        }
    }
}

#[cfg(target_os = "linux")]
fn platform_mount_roots() -> &'static [(&'static str, usize)] {
    &[("/media", 2), ("/run/media", 2), ("/mnt", 1)]
}

#[cfg(target_os = "macos")]
fn platform_mount_roots() -> &'static [(&'static str, usize)] {
    &[("/Volumes", 1)]
}

#[cfg(target_os = "windows")]
fn get_windows_volume_serial_number(drive_root: &str) -> Option<String> {
    let wide_drive = to_wide(drive_root);
    let mut serial_number = 0u32;

    let ok = unsafe {
        GetVolumeInformationW(
            PCWSTR(wide_drive.as_ptr()),
            None,
            Some(&mut serial_number),
            None,
            None,
            None,
        )
    }
    .is_ok();

    if !ok {
        return None;
    }

    Some(format!("{serial_number:08X}"))
}

#[cfg(target_os = "windows")]
fn to_wide(value: &str) -> Vec<u16> {
    value.encode_utf16().chain(std::iter::once(0)).collect()
}

#[cfg(any(target_os = "linux", target_os = "macos"))]
fn get_unix_usb_serial(mount_path: &Path) -> Option<String> {
    #[cfg(target_os = "linux")]
    {
        get_linux_usb_serial(mount_path)
    }

    #[cfg(target_os = "macos")]
    {
        get_macos_usb_serial(mount_path)
    }
}

#[cfg(target_os = "linux")]
fn get_linux_usb_serial(mount_path: &Path) -> Option<String> {
    let mount_str = mount_path.to_str()?;
    let source = run_command("findmnt", &["-n", "-o", "SOURCE", "--target", mount_str])?;

    if let Some(serial) = read_udev_property(&source, "ID_SERIAL_SHORT") {
        return Some(serial);
    }

    if let Some(serial) = read_udev_property(&source, "ID_SERIAL") {
        return Some(serial);
    }

    let lsblk_output = run_command("lsblk", &["-n", "-o", "SERIAL,UUID", &source])?;
    for part in lsblk_output.split_whitespace() {
        let candidate = sanitize_serial(part);
        if !candidate.is_empty() {
            return Some(candidate);
        }
    }

    None
}

#[cfg(target_os = "linux")]
fn read_udev_property(device_path: &str, property_name: &str) -> Option<String> {
    let output = run_command("udevadm", &["info", "--query=property", "--name", device_path])?;
    let prefix = format!("{property_name}=");

    for line in output.lines() {
        if let Some(value) = line.strip_prefix(&prefix) {
            let candidate = sanitize_serial(value);
            if !candidate.is_empty() {
                return Some(candidate);
            }
        }
    }

    None
}

#[cfg(target_os = "macos")]
fn get_macos_usb_serial(mount_path: &Path) -> Option<String> {
    let mount_str = mount_path.to_str()?;
    let output = run_command("diskutil", &["info", mount_str])?;

    for key in [
        "Disk / Partition UUID:",
        "Volume UUID:",
        "Media UUID:",
        "Device Identifier:",
    ] {
        if let Some(value) = extract_colon_value(&output, key) {
            let candidate = sanitize_serial(&value);
            if !candidate.is_empty() {
                return Some(candidate);
            }
        }
    }

    None
}

#[cfg(any(target_os = "linux", target_os = "macos"))]
fn run_command(command: &str, arguments: &[&str]) -> Option<String> {
    let output = Command::new(command).args(arguments).output().ok()?;
    if !output.status.success() {
        return None;
    }

    let stdout = String::from_utf8(output.stdout).ok()?;
    let trimmed = stdout.trim().to_owned();
    if trimmed.is_empty() {
        return None;
    }

    Some(trimmed)
}

#[cfg(target_os = "macos")]
fn extract_colon_value(output: &str, key: &str) -> Option<String> {
    for line in output.lines() {
        let trimmed = line.trim();
        if let Some(value) = trimmed.strip_prefix(key) {
            let candidate = value.trim().to_owned();
            if !candidate.is_empty() {
                return Some(candidate);
            }
        }
    }

    None
}

fn sanitize_serial(value: &str) -> String {
    value
        .trim()
        .chars()
        .map(|ch| {
            if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' || ch == '.' {
                ch
            } else {
                '-'
            }
        })
        .collect::<String>()
        .trim_matches('-')
        .to_uppercase()
}

fn app_icon() -> egui::IconData {
    if let Ok(image) = image::load_from_memory_with_format(
        include_bytes!("../assets/app-icon.png"),
        ImageFormat::Png,
    ) {
        let rgba = image.into_rgba8();
        let (width, height) = rgba.dimensions();

        return egui::IconData {
            rgba: rgba.into_raw(),
            width,
            height,
        };
    }

    fallback_app_icon()
}

fn fallback_app_icon() -> egui::IconData {
    let width = 64;
    let height = 64;
    let mut rgba = Vec::with_capacity(width * height * 4);

    for y in 0..height {
        for x in 0..width {
            let (r, g, b) = if x < 16 || x > 47 || y < 14 || y > 49 {
                (24, 71, 140)
            } else if x > 24 && x < 40 && y > 20 && y < 44 {
                (255, 255, 255)
            } else {
                (51, 123, 214)
            };

            rgba.extend_from_slice(&[r, g, b, 255]);
        }
    }

    egui::IconData {
        rgba,
        width: width as u32,
        height: height as u32,
    }
}
