//! QR Code Generator for TOTP Enrollment
//!
//! Generates QR codes for TOTP secret enrollment in authenticator apps.

use anyhow::Result;
use qrcode::{render::svg, QrCode};
use thiserror::Error;

/// QR code generation errors
#[derive(Debug, Error)]
pub enum QrCodeError {
    #[error("QR code generation failed: {0}")]
    GenerationFailed(String),

    #[error("Invalid URI format: {0}")]
    InvalidUri(String),

    #[error("Rendering failed: {0}")]
    RenderingFailed(String),
}

/// QR code generator for TOTP enrollment
pub struct QrCodeGenerator {
    /// Image size in pixels
    image_size: u32,
    /// Error correction level
    error_correction: qrcode::EcLevel,
}

impl QrCodeGenerator {
    /// Create new QR code generator with default settings
    pub fn new() -> Self {
        Self {
            image_size: 256,
            error_correction: qrcode::EcLevel::M, // Medium error correction
        }
    }

    /// Create QR code generator with custom size
    pub fn with_size(size: u32) -> Self {
        Self {
            image_size: size,
            error_correction: qrcode::EcLevel::M,
        }
    }

    /// Generate PNG image from otpauth:// URI
    pub fn generate_png(&self, uri: &str) -> Result<Vec<u8>> {
        // Validate URI format
        if !uri.starts_with("otpauth://") {
            return Err(
                QrCodeError::InvalidUri("URI must start with otpauth://".to_string()).into(),
            );
        }

        // Create QR code
        let code = QrCode::with_error_correction_level(uri, self.error_correction)
            .map_err(|e| QrCodeError::GenerationFailed(e.to_string()))?;

        // Render to PNG
        let image = code
            .render::<image::Luma<u8>>()
            .min_dimensions(self.image_size, self.image_size)
            .build();

        // Convert to PNG bytes
        let mut png_bytes = Vec::new();
        image
            .write_to(
                &mut std::io::Cursor::new(&mut png_bytes),
                image::ImageFormat::Png,
            )
            .map_err(|e| QrCodeError::RenderingFailed(e.to_string()))?;

        Ok(png_bytes)
    }

    /// Generate SVG image from otpauth:// URI
    pub fn generate_svg(&self, uri: &str) -> Result<String> {
        // Validate URI format
        if !uri.starts_with("otpauth://") {
            return Err(
                QrCodeError::InvalidUri("URI must start with otpauth://".to_string()).into(),
            );
        }

        // Create QR code
        let code = QrCode::with_error_correction_level(uri, self.error_correction)
            .map_err(|e| QrCodeError::GenerationFailed(e.to_string()))?;

        // Render to SVG
        let svg = code
            .render()
            .min_dimensions(self.image_size, self.image_size)
            .dark_color(svg::Color("#000000"))
            .light_color(svg::Color("#ffffff"))
            .build();

        Ok(svg)
    }

    /// Generate data URL for inline display (base64 encoded PNG)
    pub fn generate_data_url(&self, uri: &str) -> Result<String> {
        let png_bytes = self.generate_png(uri)?;
        let base64_encoded =
            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &png_bytes);

        Ok(format!("data:image/png;base64,{}", base64_encoded))
    }

    /// Set image size
    pub fn set_size(&mut self, size: u32) {
        self.image_size = size;
    }

    /// Set error correction level
    pub fn set_error_correction(&mut self, level: qrcode::EcLevel) {
        self.error_correction = level;
    }
}

impl Default for QrCodeGenerator {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_generate_png() {
        let generator = QrCodeGenerator::new();
        let uri =
            "otpauth://totp/FoxhuntHFT:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=FoxhuntHFT";

        let png_bytes = generator.generate_png(uri).unwrap();

        // PNG should have magic bytes
        assert_eq!(&png_bytes[0..8], b"\x89PNG\r\n\x1a\n");

        // Should be a valid size
        assert!(png_bytes.len() > 100);
    }

    #[test]
    fn test_generate_svg() {
        let generator = QrCodeGenerator::new();
        let uri =
            "otpauth://totp/FoxhuntHFT:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=FoxhuntHFT";

        let svg = generator.generate_svg(uri).unwrap();

        // SVG should contain proper XML
        assert!(svg.contains("<svg"));
        assert!(svg.contains("</svg>"));
    }

    #[test]
    fn test_generate_data_url() {
        let generator = QrCodeGenerator::new();
        let uri =
            "otpauth://totp/FoxhuntHFT:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=FoxhuntHFT";

        let data_url = generator.generate_data_url(uri).unwrap();

        assert!(data_url.starts_with("data:image/png;base64,"));
    }

    #[test]
    fn test_invalid_uri() {
        let generator = QrCodeGenerator::new();
        let invalid_uri = "https://example.com/invalid";

        assert!(generator.generate_png(invalid_uri).is_err());
        assert!(generator.generate_svg(invalid_uri).is_err());
    }

    #[test]
    fn test_custom_size() {
        let generator = QrCodeGenerator::with_size(512);
        assert_eq!(generator.image_size, 512);
    }
}
