use std::collections::HashMap;
use std::thread;
use std::time::Duration;

use calloop::channel::{self, Sender};
use calloop::LoopHandle;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Size};
use tracing::{error, info, warn};
use zbus::zvariant::OwnedValue;

use crate::drawing::{ForgeElement, RenderTexture, Texture};
use crate::forge::Forge;
use crate::output::Canvas;
use crate::text::TextRenderer;

// ── D-Bus agent interface ─────────────────────────────────────────────────────

struct AgentInterface
{
    sender: Sender<PolkitEvent>,
}

#[zbus::interface(name = "org.freedesktop.PolicyKit1.AuthenticationAgent")]
impl AgentInterface
{
    fn begin_authentication(
        &self,
        _action_id:   &str,
        message:       &str,
        icon_name:     &str,
        _details:      HashMap<String, String>,
        cookie:        &str,
        _identities:   Vec<(String, HashMap<String, OwnedValue>)>,
    )
    {
        let _ = self.sender.send( PolkitEvent::AuthRequest( PolkitRequest {
            message: message.to_string(),
            icon:    icon_name.to_string(),
            cookie:  cookie.to_string(),
        }));
    }

    fn cancel_authentication( &self, cookie: &str )
    {
        let _ = self.sender.send( PolkitEvent::AuthCancel( cookie.to_string() ) );
    }
}

const DIALOG_BG: [u8; 4] = [30, 30, 30, 245];
const OVERLAY_BG: [u8; 4] = [0, 0, 0, 160];
const INPUT_BG: [u8; 4] = [50, 50, 50, 255];
const BUTTON_BG: [u8; 4] = [60, 100, 180, 255];
const CANCEL_BG: [u8; 4] = [80, 80, 80, 255];
const TEXT_COLOR: [u8; 4] = [255, 255, 255, 255];
const LABEL_COLOR: [u8; 4] = [200, 200, 200, 255];

const DIALOG_WIDTH: i32 = 360;
const DIALOG_HEIGHT: i32 = 280;
const BUTTON_HEIGHT: i32 = 44;
const INPUT_HEIGHT: i32 = 40;
const PADDING: i32 = 16;

#[derive(Debug, Clone)]
pub struct PolkitRequest {
    pub message: String,
    /// Icon name carried in the auth request; rendered once the agent UI
    /// is wired. See [`run_polkit_agent`] (currently a stub).
    #[allow(dead_code)]
    pub icon: String,
    pub cookie: String,
}

// Auth-dialog scaffolding. `run_polkit_agent` does not yet register a
// polkit agent on the bus, so these events are never emitted; kept as the
// wiring target for the interactive authentication dialog.
#[allow(dead_code)]
#[derive(Debug)]
pub enum PolkitEvent {
    AuthRequest(PolkitRequest),
    AuthCancel(String),
}

pub struct PolkitAgent {
    pub visible: bool,
    pub request: Option<PolkitRequest>,
    password_buffer: String,
    error: Option<String>,
    dirty: bool,
    last_scale: f64,

    overlay_bg: RenderTexture,
    dialog_bg: RenderTexture,
    input_bg: RenderTexture,
    button_bg: RenderTexture,
    cancel_bg: RenderTexture,
    title_texture: Option<RenderTexture>,
    message_texture: Option<RenderTexture>,
    dots_texture: Option<RenderTexture>,
    error_texture: Option<RenderTexture>,
    auth_label: Option<RenderTexture>,
    cancel_label: Option<RenderTexture>,
}

impl PolkitAgent {
    pub fn new() -> Self {
        Self {
            visible: false,
            request: None,
            password_buffer: String::new(),
            error: None,
            dirty: true,
            last_scale: 0.0,
            overlay_bg: RenderTexture::new(Texture::from_color(OVERLAY_BG, false)),
            dialog_bg: RenderTexture::new(Texture::from_color(DIALOG_BG, true)),
            input_bg: RenderTexture::new(Texture::from_color(INPUT_BG, true)),
            button_bg: RenderTexture::new(Texture::from_color(BUTTON_BG, true)),
            cancel_bg: RenderTexture::new(Texture::from_color(CANCEL_BG, true)),
            title_texture: None,
            message_texture: None,
            dots_texture: None,
            error_texture: None,
            auth_label: None,
            cancel_label: None,
        }
    }

    /// Start polkit agent listener.
    pub fn start_agent(event_loop: &LoopHandle<'static, Forge>) {
        let (sender, channel) = channel::channel::<PolkitEvent>();

        event_loop
            .insert_source(channel, |event, _, forge| {
                if let channel::Event::Msg(evt) = event {
                    match evt {
                        PolkitEvent::AuthRequest(req) => {
                            forge.polkit.request = Some(req);
                            forge.polkit.visible = true;
                            forge.polkit.password_buffer.clear();
                            forge.polkit.error = None;
                            forge.polkit.dirty = true;
                        }
                        PolkitEvent::AuthCancel(_cookie) => {
                            forge.polkit.visible = false;
                            forge.polkit.request = None;
                            forge.polkit.dirty = true;
                        }
                    }
                    forge.windows.set_dirty();
                    forge.unstall();
                }
            })
            .expect("polkit channel");

        thread::Builder::new()
            .name("forge-polkit".into())
            .spawn(move || {
                if let Err(e) = run_polkit_agent(sender) {
                    error!("Polkit agent failed: {e}");
                }
            })
            .expect("polkit thread");
    }

    pub fn push_char(&mut self, c: char) {
        if self.password_buffer.len() < 128 {
            self.password_buffer.push(c);
            self.dirty = true;
        }
    }

    pub fn pop_char(&mut self) {
        self.password_buffer.pop();
        self.dirty = true;
    }

    pub fn cancel(&mut self) {
        self.visible = false;
        self.request = None;
        self.password_buffer.clear();
        self.dirty = true;
    }

    /// Try to authenticate with the entered password. Returns true on success.
    pub fn try_authenticate(&mut self) -> bool {
        if self.password_buffer.is_empty() {
            return false;
        }

        // Use pkexec or polkit helper to verify password
        let verified = crate::pam_verify::verify_password(&self.password_buffer);

        if verified {
            // Send authentication response back via D-Bus.
            // Args passed directly to dbus-send — no shell involved.
            if let Some(req) = &self.request {
                let _ = std::process::Command::new("dbus-send")
                    .args([
                        "--system",
                        "--type=method_call",
                        "--dest=org.freedesktop.PolicyKit1",
                        "/org/freedesktop/PolicyKit1/Authority",
                        "org.freedesktop.PolicyKit1.Authority.AuthenticationAgentResponse",
                        &format!("string:{}", req.cookie),
                        &format!("string:{}", self.password_buffer),
                    ])
                    .spawn();
            }
            self.visible = false;
            self.request = None;
            self.password_buffer.clear();
            true
        } else {
            self.error = Some("Authentication failed".into());
            self.password_buffer.clear();
            self.dirty = true;
            false
        }
    }

    pub fn handle_tap(&self, canvas: &Canvas, point: Point<f64, Logical>) -> Option<PolkitAction> {
        if !self.visible {
            return None;
        }

        let size = canvas.size();
        let dw = if canvas.is_mobile() { size.w - 32 } else { DIALOG_WIDTH };
        let dx = (size.w - dw) / 2;
        let dy = (size.h - DIALOG_HEIGHT) / 2;

        // Authenticate button
        let auth_y = dy + DIALOG_HEIGHT - BUTTON_HEIGHT - PADDING;
        let auth_rect = Rectangle::new(
            Point::from((dx + PADDING, auth_y)),
            Size::from(((dw - 3 * PADDING) / 2, BUTTON_HEIGHT)),
        );
        if auth_rect.to_f64().contains(point) {
            return Some(PolkitAction::Authenticate);
        }

        // Cancel button
        let cancel_rect = Rectangle::new(
            Point::from((dx + dw / 2 + PADDING / 2, auth_y)),
            Size::from(((dw - 3 * PADDING) / 2, BUTTON_HEIGHT)),
        );
        if cancel_rect.to_f64().contains(point) {
            return Some(PolkitAction::Cancel);
        }

        Some(PolkitAction::None)
    }

    fn update_textures(
        &mut self,
        renderer: &mut GlesRenderer,
        canvas: &Canvas,
        text_renderer: &mut TextRenderer,
    ) {
        let scale = canvas.scale();
        if !self.dirty && (self.last_scale - scale).abs() < 0.01 {
            return;
        }
        self.dirty = false;
        self.last_scale = scale;

        let title_font = (16.0 * scale as f32).round();
        let msg_font = (12.0 * scale as f32).round();
        let btn_font = (14.0 * scale as f32).round();
        let dot_font = (18.0 * scale as f32).round();

        // Title
        let (buf, w, h) = text_renderer.render_line("Authentication Required", title_font, TEXT_COLOR);
        if w > 0 && h > 0 {
            self.title_texture = Some(RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ));
        }

        // Message from polkit request
        if let Some(req) = &self.request {
            let (buf, w, h) = text_renderer.render_line(&req.message, msg_font, LABEL_COLOR);
            if w > 0 && h > 0 {
                self.message_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        }

        // Password dots
        if !self.password_buffer.is_empty() {
            let dots: String = "* ".repeat(self.password_buffer.len()).trim_end().to_string();
            let (buf, w, h) = text_renderer.render_line(&dots, dot_font, TEXT_COLOR);
            if w > 0 && h > 0 {
                self.dots_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        } else {
            self.dots_texture = None;
        }

        // Error
        if let Some(err) = &self.error {
            let (buf, w, h) = text_renderer.render_line(err, msg_font, [255, 80, 80, 255]);
            if w > 0 && h > 0 {
                self.error_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        } else {
            self.error_texture = None;
        }

        // Button labels
        let (buf, w, h) = text_renderer.render_line("Authenticate", btn_font, TEXT_COLOR);
        if w > 0 && h > 0 {
            self.auth_label = Some(RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ));
        }
        let (buf, w, h) = text_renderer.render_line("Cancel", btn_font, TEXT_COLOR);
        if w > 0 && h > 0 {
            self.cancel_label = Some(RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ));
        }
    }

    pub fn textures(
        &mut self,
        textures: &mut Vec<ForgeElement>,
        canvas: &Canvas,
        renderer: &mut GlesRenderer,
        text_renderer: &mut TextRenderer,
    ) {
        if !self.visible {
            return;
        }

        self.update_textures(renderer, canvas, text_renderer);

        let scale = canvas.scale();
        let size = canvas.size();

        // Overlay
        let overlay_rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((0, 0)), size);
        let physical_overlay = overlay_rect.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.overlay_bg.clone(), physical_overlay.loc, physical_overlay, None, scale);

        // Dialog box
        let dw = if canvas.is_mobile() { size.w - 32 } else { DIALOG_WIDTH };
        let dx = (size.w - dw) / 2;
        let dy = (size.h - DIALOG_HEIGHT) / 2;

        let dialog_rect: Rectangle<i32, Logical> = Rectangle::new(
            Point::from((dx, dy)),
            Size::from((dw, DIALOG_HEIGHT)),
        );
        let physical_dialog = dialog_rect.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.dialog_bg.clone(), physical_dialog.loc, physical_dialog, None, scale);

        let mut text_y = dy + PADDING;

        // Title
        if let Some(title) = &self.title_texture {
            let ts = title.size();
            let tx = dx + (dw - ts.w) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((tx, text_y)), ts);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, title.clone(), physical.loc, physical, None, scale);
            text_y += ts.h + 12;
        }

        // Message
        if let Some(msg) = &self.message_texture {
            let ms = msg.size();
            let mx = dx + PADDING;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((mx, text_y)), ms);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, msg.clone(), physical.loc, physical, None, scale);
            text_y += ms.h + 16;
        }

        // Password input field
        let input_rect: Rectangle<i32, Logical> = Rectangle::new(
            Point::from((dx + PADDING, text_y)),
            Size::from((dw - 2 * PADDING, INPUT_HEIGHT)),
        );
        let physical_input = input_rect.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.input_bg.clone(), physical_input.loc, physical_input, None, scale);

        // Password dots inside input
        if let Some(dots) = &self.dots_texture {
            let ds = dots.size();
            let dots_x = dx + PADDING + 8;
            let dots_y = text_y + (INPUT_HEIGHT - ds.h) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((dots_x, dots_y)), ds);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, dots.clone(), physical.loc, physical, None, scale);
        }

        text_y += INPUT_HEIGHT + 8;

        // Error message
        if let Some(err) = &self.error_texture {
            let es = err.size();
            let ex = dx + PADDING;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((ex, text_y)), es);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, err.clone(), physical.loc, physical, None, scale);
        }

        // Buttons at bottom
        let auth_y = dy + DIALOG_HEIGHT - BUTTON_HEIGHT - PADDING;
        let half_w = (dw - 3 * PADDING) / 2;

        // Authenticate button
        let auth_rect: Rectangle<i32, Logical> = Rectangle::new(
            Point::from((dx + PADDING, auth_y)),
            Size::from((half_w, BUTTON_HEIGHT)),
        );
        let physical_auth = auth_rect.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.button_bg.clone(), physical_auth.loc, physical_auth, None, scale);

        if let Some(label) = &self.auth_label {
            let ls = label.size();
            let lx = dx + PADDING + (half_w - ls.w) / 2;
            let ly = auth_y + (BUTTON_HEIGHT - ls.h) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((lx, ly)), ls);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, label.clone(), physical.loc, physical, None, scale);
        }

        // Cancel button
        let cancel_rect: Rectangle<i32, Logical> = Rectangle::new(
            Point::from((dx + dw / 2 + PADDING / 2, auth_y)),
            Size::from((half_w, BUTTON_HEIGHT)),
        );
        let physical_cancel = cancel_rect.to_physical_precise_round(scale);
        ForgeElement::add_element(textures, self.cancel_bg.clone(), physical_cancel.loc, physical_cancel, None, scale);

        if let Some(label) = &self.cancel_label {
            let ls = label.size();
            let lx = dx + dw / 2 + PADDING / 2 + (half_w - ls.w) / 2;
            let ly = auth_y + (BUTTON_HEIGHT - ls.h) / 2;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((lx, ly)), ls);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, label.clone(), physical.loc, physical, None, scale);
        }
    }
}

pub enum PolkitAction {
    Authenticate,
    Cancel,
    None,
}

fn run_polkit_agent(sender: Sender<PolkitEvent>) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
{
    info!("Polkit agent: connecting to system bus");

    let conn = zbus::blocking::Connection::system()?;

    // Export the authentication agent interface so polkit can call us back.
    conn.object_server().at(
        "/org/freedesktop/PolicyKit1/AuthenticationAgent",
        AgentInterface { sender },
    )?;

    // Identify this session to polkit.
    let session_id = std::env::var( "XDG_SESSION_ID" ).unwrap_or_else( |_| "1".to_string() );
    let subject_details: HashMap<&str, zbus::zvariant::Value<'_>> =
        HashMap::from([ ("session-id", zbus::zvariant::Value::new( session_id.as_str() )) ]);

    match conn.call_method(
        Some( "org.freedesktop.PolicyKit1" ),
        "/org/freedesktop/PolicyKit1/Authority",
        Some( "org.freedesktop.PolicyKit1.Authority" ),
        "RegisterAuthenticationAgent",
        &(
            ( "unix-session", &subject_details ),
            "en_US",
            "/org/freedesktop/PolicyKit1/AuthenticationAgent",
        ),
    ) {
        Ok( _ )  => info!( "Polkit agent registered (session {session_id})" ),
        Err( e ) => warn!( "Polkit agent registration failed: {e} — auth dialogs may not appear" ),
    }

    // Hold the connection alive; zbus dispatches incoming calls on its
    // internal thread as long as `conn` is live.
    loop {
        thread::sleep( Duration::from_secs( 60 ) );
    }
}
