<?php
namespace WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Helpers;

use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
use BaconQrCode\Encoder\QrCode;
use Exception;

/**
 * QR Code Generator for 2FA
 *
 * Generates QR codes for TOTP/HOTP provisioning locally using bacon/bacon-qr-code
 */
class QRCodeGenerator
{
    /**
     * Generate QR code locally for 2FA provisioning
     *
     * @param string $data Data to encode in QR code (usually TOTP URI)
     * @param int $size QR code size in pixels (default: 200)
     * @param int $margin QR code margin (default: 1)
     * @return string Base64 encoded data URI
     * @throws Exception If QR code generation fails
     */
    public static function generate(string $data, int $size = 200, int $margin = 1): string
    {
        try {
            // Create renderer with Imagick backend
            $renderer = new ImageRenderer(
                new RendererStyle($size, $margin),
                new ImagickImageBackEnd()
            );

            // Create writer
            $writer = new Writer($renderer);

            // Generate QR code
            $qrCodeImage = $writer->writeString($data);

            // Convert to base64 data URI
            $base64 = base64_encode($qrCodeImage);
            $mimeType = 'image/png'; // Imagick typically outputs PNG by default

            return sprintf('data:%s;base64,%s', $mimeType, $base64);

        } catch (Exception $e) {
            // Log error for debugging
            error_log('QR Code generation failed: ' . $e->getMessage());
            
            // Fallback to external API if local generation fails
            return self::generateExternalFallback($data, $size);
        }
    }

    /**
     * Generate QR code using GD backend (fallback if Imagick not available)
     *
     * @param string $data Data to encode
     * @param int $size QR code size
     * @return string Base64 encoded data URI
     * @throws Exception If QR code generation fails
     */
    public static function generateWithGD(string $data, int $size = 200): string
    {
        try {
            // Use GD backend as fallback
            $renderer = new ImageRenderer(
                new RendererStyle($size, 1),
                new \BaconQrCode\Renderer\Image\SvgImageBackEnd()
            );

            $writer = new Writer($renderer);
            $qrCodeSvg = $writer->writeString($data);

            // Convert SVG to PNG using GD
            $svgData = simplexml_load_string($qrCodeSvg);
            if (!$svgData) {
                throw new Exception('Failed to parse SVG data');
            }

            // Create image from SVG dimensions
            $width = (int) $svgData['width'];
            $height = (int) $svgData['height'];
            
            $image = imagecreatetruecolor($width, $height);
            if (!$image) {
                throw new Exception('Failed to create GD image');
            }

            // Set white background
            $white = imagecolorallocate($image, 255, 255, 255);
            imagefill($image, 0, 0, $white);

            // Set black color for QR code
            $black = imagecolorallocate($image, 0, 0, 0);

            // Process SVG paths to draw QR code
            // This is a simplified approach - in production, you might want to use a proper SVG to raster converter
            foreach ($svgData->rect as $rect) {
                $x = (int) $rect['x'];
                $y = (int) $rect['y'];
                $w = (int) $rect['width'];
                $h = (int) $rect['height'];
                
                if ($rect['fill'] === '#000000') {
                    imagefilledrectangle($image, $x, $y, $x + $w - 1, $y + $h - 1, $black);
                }
            }

            // Convert to base64
            ob_start();
            imagepng($image);
            $pngData = ob_get_clean();
            imagedestroy($image);

            $base64 = base64_encode($pngData);
            return sprintf('data:image/png;base64,%s', $base64);

        } catch (Exception $e) {
            error_log('GD QR Code generation failed: ' . $e->getMessage());
            
            // Fallback to external API
            return self::generateExternalFallback($data, $size);
        }
    }

    /**
     * Generate QR code with custom colors
     *
     * @param string $data Data to encode
     * @param string $foregroundColor Foreground color in hex (default: #000000)
     * @param string $backgroundColor Background color in hex (default: #ffffff)
     * @param int $size QR code size
     * @return string Base64 encoded data URI
     */
    public static function generateWithColors(string $data, string $foregroundColor = '#000000', string $backgroundColor = '#ffffff', int $size = 200): string
    {
        try {
            // Convert hex to RGB
            $fgRgb = self::hexToRgb($foregroundColor);
            $bgRgb = self::hexToRgb($backgroundColor);

            // Create custom renderer style with colors
            $rendererStyle = new RendererStyle($size, 1);
            
            $renderer = new ImageRenderer(
                $rendererStyle,
                new ImagickImageBackEnd()
            );

            $writer = new Writer($renderer);
            $qrCodeImage = $writer->writeString($data);

            // Apply color filters using Imagick
            $imagick = new \Imagick();
            $imagick->readImageBlob($qrCodeImage);
            
            // Apply color mapping
            $imagick->opaquePaintImage(
                new \ImagickPixel('black'),
                new \ImagickPixel($foregroundColor),
                0,
                false
            );
            
            $imagick->opaquePaintImage(
                new \ImagickPixel('white'),
                new \ImagickPixel($backgroundColor),
                0,
                false
            );

            // Convert to base64 data URI
            $imageData = $imagick->getImageBlob();
            $base64 = base64_encode($imageData);
            $mimeType = 'image/png';

            $imagick->destroy();

            return sprintf('data:%s;base64,%s', $mimeType, $base64);

        } catch (Exception $e) {
            error_log('Colored QR Code generation failed: ' . $e->getMessage());
            
            // Fallback to standard generation
            return self::generate($data, $size);
        }
    }

    /**
     * Generate QR code for TOTP URI
     *
     * @param string $secret TOTP secret
     * @param string $label Account label (e.g., "user@example.com")
     * @param string $issuer Issuer name (e.g., "My Company")
     * @param string $algorithm Algorithm (default: sha1)
     * @param int $digits Number of digits (default: 6)
     * @param int $period Time period (default: 30)
     * @return string Base64 encoded data URI
     */
    public static function generateTOTPQRCode(
        string $secret, 
        string $label, 
        string $issuer = '', 
        string $algorithm = 'sha1',
        int $digits = 6,
        int $period = 30
    ): string {
        // Build TOTP URI
        $uri = 'otpauth://totp/';
        
        if (!empty($issuer)) {
            $uri .= rawurlencode($issuer) . ':';
        }
        
        $uri .= rawurlencode($label);
        
        $params = [
            'secret' => $secret,
            'algorithm' => strtoupper($algorithm),
            'digits' => $digits,
            'period' => $period
        ];
        
        if (!empty($issuer)) {
            $params['issuer'] = $issuer;
        }
        
        $uri .= '?' . http_build_query($params);
        
        return self::generate($uri);
    }

    /**
     * External API fallback for QR code generation
     *
     * @param string $data Data to encode
     * @param int $size QR code size
     * @return string External QR code URL
     */
    private static function generateExternalFallback(string $data, int $size = 200): string
    {
        return sprintf(
            'https://api.qrserver.com/v1/create-qr-code/?data=%s&size=%dx%d',
            urlencode($data),
            $size,
            $size
        );
    }

    /**
     * Convert hex color to RGB
     *
     * @param string $hex Hex color string
     * @return array RGB values [r, g, b]
     * @throws Exception If hex color is invalid
     */
    private static function hexToRgb(string $hex): array
    {
        // Remove # if present
        $hex = ltrim($hex, '#');
        
        // Validate hex
        if (!preg_match('/^[0-9A-Fa-f]{6}$/', $hex)) {
            throw new Exception('Invalid hex color format: ' . $hex);
        }
        
        return [
            'r' => hexdec(substr($hex, 0, 2)),
            'g' => hexdec(substr($hex, 2, 2)),
            'b' => hexdec(substr($hex, 4, 2))
        ];
    }

    /**
     * Check if required dependencies are available
     *
     * @return array Available methods and their status
     */
    public static function checkDependencies(): array
    {
        return [
            'imagick' => extension_loaded('imagick'),
            'gd' => extension_loaded('gd'),
            'bacon_qr_code' => class_exists('\BaconQrCode\Writer'),
            'imagick_backend' => class_exists('\BaconQrCode\Renderer\Image\ImagickImageBackEnd'),
            'svg_backend' => class_exists('\BaconQrCode\Renderer\Image\SvgImageBackEnd'),
        ];
    }

    /**
     * Get recommended renderer based on available extensions
     *
     * @return string Recommended renderer method
     */
    public static function getRecommendedRenderer(): string
    {
        $deps = self::checkDependencies();
        
        if ($deps['imagick'] && $deps['imagick_backend']) {
            return 'imagick';
        } elseif ($deps['gd'] && $deps['svg_backend']) {
            return 'gd';
        } elseif ($deps['bacon_qr_code']) {
            return 'svg';
        } else {
            return 'external';
        }
    }
}