using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Text;
using Inspectron.Epson.PrintServer.PrintServices;

namespace Inspectron.Epson;

/// <summary>
/// HTML-based printer implementation that outputs to an HTML file,
/// simulating Epson thermal printer output for testing and preview purposes.
/// </summary>
public class HtmlPrinter : IEpsonPrinter
{
    private readonly ILogger? _logger;
    private readonly byte _id;
    private readonly string _outputPath;
    private readonly int _paperWidth;
    private readonly string _imageDirectory;

    private bool _isConnected;
    private readonly StringBuilder _content = new();
    private int _imageCounter;

    // Current styling state
    private EpsonCommands.PrinterFont _currentFont = EpsonCommands.PrinterFont.A;
    private int _fontWidthMultiplier = 1;
    private int _fontHeightMultiplier = 1;
    private bool _isEmphasized;
    private bool _isRedColor;
    private bool _isQuadrupleMode;
    private bool _isBiggerFontWidthTM220;
    private bool _isBiggerFontHeightTM220;
    private int _lineSpacing = 14; // Default line spacing in pixels (scaled for screen)
    private int _absolutePosition; // Current X position offset

    // Loaded image buffer
    private Image<Rgba32>? _loadedImage;
    private int _loadedImageMaxWidth;

    /// <summary>
    /// Font size mappings for different Epson fonts (scaled for screen display)
    /// Original dots: Font A: 12x24, Font B: 9x17, Font C: 9x24, Font D: 10x24, Font E: 12x24
    /// Scaled to ~0.5x for reasonable screen display
    /// </summary>
    private static readonly Dictionary<EpsonCommands.PrinterFont, (int Width, int Height)> FontSizes = new()
    {
        { EpsonCommands.PrinterFont.A, (6, 12) },
        { EpsonCommands.PrinterFont.B, (5, 9) },
        { EpsonCommands.PrinterFont.C, (5, 12) },
        { EpsonCommands.PrinterFont.D, (5, 12) },
        { EpsonCommands.PrinterFont.E, (6, 12) }
    };

    /// <inheritdoc />
    public bool IsConnected => _isConnected;

    /// <summary>
    /// Initialize a new instance of HtmlPrinter
    /// </summary>
    /// <param name="outputPath">Path to the output HTML file</param>
    /// <param name="paperWidth">Paper width in pixels (default 384 for 80mm thermal printer)</param>
    /// <param name="logger">Optional logger for diagnostic output</param>
    public HtmlPrinter(string outputPath, int paperWidth = 384, ILogger? logger = null, byte id=0x01)
    {
        _outputPath = outputPath;
        _paperWidth = paperWidth;
        _logger = logger;
        _id = id;

        // Create image directory next to HTML file
        var directory = Path.GetDirectoryName(outputPath) ?? ".";
        var fileName = Path.GetFileNameWithoutExtension(outputPath);
        _imageDirectory = Path.Combine(directory, $"{fileName}_images");
    }

    /// <inheritdoc />
    public Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10)
    {
        _logger?.LogInformation("HtmlPrinter: Connecting (output: {Path}, paper width: {Width}px)", _outputPath, _paperWidth);

        // Create image directory if it doesn't exist
        if (!Directory.Exists(_imageDirectory))
        {
            Directory.CreateDirectory(_imageDirectory);
        }

        // Clear previous content
        _content.Clear();
        _imageCounter = 0;
        ResetStylingState();

        _isConnected = true;
        WriteHtmlFile();

        _logger?.LogInformation("HtmlPrinter: Connected successfully");
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public void Disconnect()
    {
        _logger?.LogDebug("HtmlPrinter: Disconnecting");

        if (_isConnected)
        {
            WriteHtmlFile();
        }

        _loadedImage?.Dispose();
        _loadedImage = null;
        _isConnected = false;
    }

    /// <inheritdoc />
    public Task<PrinterStatus> GetPrinterStatusAsync()
    {
        _logger?.LogDebug("HtmlPrinter: Returning mock printer status (always ready)");
        return Task.FromResult(new PrinterStatus
        {
            RawByte = 0x12, // Online, cover closed
            DrawerOpen = false,
            IsOnline = true,
            IsCoverClosed = true,
            PaperFeedButtonPressed = false
        });
    }

    /// <inheritdoc />
    public Task<OfflineStatus> GetOfflineStatusAsync()
    {
        _logger?.LogDebug("HtmlPrinter: Returning mock offline status (no issues)");
        return Task.FromResult(new OfflineStatus
        {
            RawByte = 0x12,
            CoverOpen = false,
            PaperFeedButton = false,
            PaperEnd = false,
            ErrorOccurred = false
        });
    }

    /// <inheritdoc />
    public Task<ErrorStatus> GetErrorStatusAsync()
    {
        _logger?.LogDebug("HtmlPrinter: Returning mock error status (no errors)");
        return Task.FromResult(new ErrorStatus
        {
            RawByte = 0x12,
            RecoverableError = false,
            AutoCutterError = false,
            UnrecoverableError = false,
            AutoRecoveryError = false
        });
    }

    /// <inheritdoc />
    public Task<PaperSensorStatus> GetPaperSensorStatusAsync()
    {
        _logger?.LogDebug("HtmlPrinter: Returning mock paper sensor status (paper present)");
        return Task.FromResult(new PaperSensorStatus
        {
            RawByte = 0x12,
            PaperNearEnd = false,
            PaperPresent = true
        });
    }

    /// <inheritdoc />
    public Task<byte> GetTM220StatusAsync()
    {
        _logger?.LogDebug("HtmlPrinter: Returning mock TM220 status");
        return Task.FromResult((byte)0x12);
    }

    /// <inheritdoc />
    public Task<byte?> GetPrinterIdAsync()
    {
        return Task.FromResult<byte?>(_id);
    }

    /// <inheritdoc />
    public Task<OverallStatus> GetOverallStatusAsync()
    {
        _logger?.LogInformation("HtmlPrinter: Returning mock overall status (ready)");
        return Task.FromResult(new OverallStatus
        {
            PrinterModel = "HtmlPrinter",
            PrinterStatus = new PrinterStatus { RawByte = 0x12, IsOnline = true, IsCoverClosed = true },
            OfflineStatus = new OfflineStatus { RawByte = 0x12 },
            ErrorStatus = new ErrorStatus { RawByte = 0x12 },
            PaperStatus = new PaperSensorStatus { RawByte = 0x12, PaperPresent = true },
            StatusText = "READY - HTML Printer Simulator",
            StatusIcon = "🟢",
            IsReady = true,
            Recommendations = new List<string>()
        });
    }

    /// <inheritdoc />
    public Task InitAsync()
    {
        _logger?.LogInformation("HtmlPrinter: Initializing (resetting styling state)");
        ResetStylingState();
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetQuadrupleMode(bool enable)
    {
        _logger?.LogInformation("HtmlPrinter: {Action} quadruple mode", enable ? "Enabling" : "Disabling");
        _isQuadrupleMode = enable;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetRedColor(bool enable)
    {
        _logger?.LogInformation("HtmlPrinter: {Action} red color mode", enable ? "Enabling" : "Disabling");
        _isRedColor = enable;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetEmphasized(bool enable)
    {
        _logger?.LogInformation("HtmlPrinter: {Action} emphasized mode", enable ? "Enabling" : "Disabling");
        _isEmphasized = enable;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SelectFont(EpsonCommands.PrinterFont font)
    {
        _logger?.LogInformation("HtmlPrinter: Selecting font {Font}", font);
        _currentFont = font;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetBiggerFontTM220(bool biggerWidth, bool biggerHeight = false, bool secondaryFont = true)
    {
        _logger?.LogInformation("HtmlPrinter: Setting bigger font mode: Width={BiggerWidth}, Height={BiggerHeight}, SecondaryFont={SecondaryFont}",
            biggerWidth, biggerHeight, secondaryFont);
        _isBiggerFontWidthTM220 = biggerWidth;
        _isBiggerFontHeightTM220 = biggerHeight;
        // Note: secondaryFont is ignored in HTML simulation as it only affects the physical printer's font selection
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task PrintTextAsync(string text)
    {
        EnsureConnected();
        _logger?.LogInformation("HtmlPrinter: Printing text ({Length} characters)", text.Length);

        var style = BuildCurrentStyle();
        var escapedText = EscapeHtml(text);

        // Handle newlines
        escapedText = escapedText.TrimEnd('\n').Replace("\n", "<br>");

        // Add margin-left for absolute positioning
        var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : "";

        _content.AppendLine($"<span style=\"{style}{positionStyle}\">{escapedText}</span>");

        // Reset absolute position after printing
        _absolutePosition = 0;

        WriteHtmlFile();
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetFontSizeAsync(int width, int height)
    {
        if (width < 1 || width > 8 || height < 1 || height > 8)
            throw new ArgumentException("Size must be between 1 and 8");

        _logger?.LogInformation("HtmlPrinter: Setting font size to {Width}x{Height}", width, height);
        _fontWidthMultiplier = width;
        _fontHeightMultiplier = height;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task PrintTextAndCutAsync(string text, int feedLines = 5)
    {
        _logger?.LogInformation("HtmlPrinter: Printing text with cut ({Length} characters, {FeedLines} feed lines)",
            text.Length, feedLines);

        await InitAsync();
        await PrintTextAsync(text);
        await PrintTextAsync("\n");
        await FeedLinesAsync(feedLines);
        await CutAsync();
    }

    /// <inheritdoc />
    public Task PrintBuffer()
    {
        _logger?.LogDebug("HtmlPrinter: PrintBuffer (no-op for HTML)");
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task FeedLinesAsync(int lines)
    {
        EnsureConnected();
        _logger?.LogInformation("HtmlPrinter: Feeding {Lines} lines", lines);

        for (int i = 0; i < lines; i++)
        {
            _content.AppendLine($"<div style=\"height: {_lineSpacing}px;\"></div>");
        }

        WriteHtmlFile();
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task CutAsync(bool fullCut = true)
    {
        EnsureConnected();
        _logger?.LogInformation("HtmlPrinter: Cutting paper");
        if(fullCut)
            WriteHtmlFile();
        else
        {
            // add scissors icon for partial cut
            _content.AppendLine("<div class=\"cut\"><span class=\"scissors\">✂️</span></div>");
        }
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task LoadImageAsync(string imagePath, int maxWidth = 384)
    {
        _logger?.LogInformation("HtmlPrinter: Loading image from file: {Path}", imagePath);

        if (!File.Exists(imagePath))
        {
            throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath);
        }

        _loadedImage?.Dispose();
        _loadedImage = await Image.LoadAsync<Rgba32>(imagePath);
        _loadedImageMaxWidth = maxWidth;
    }

    /// <inheritdoc />
    public async Task LoadImageAsync(Stream imageStream, int maxWidth = 384)
    {
        _logger?.LogInformation("HtmlPrinter: Loading image from stream");

        _loadedImage?.Dispose();
        _loadedImage = await Image.LoadAsync<Rgba32>(imageStream);
        _loadedImageMaxWidth = maxWidth;
    }

    /// <inheritdoc />
    public Task LoadImageAsync(Image<Rgba32> image, int maxWidth = 384)
    {
        _logger?.LogInformation("HtmlPrinter: Loading image from ImageSharp object");

        _loadedImage?.Dispose();
        _loadedImage = image.Clone();
        _loadedImageMaxWidth = maxWidth;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task PrintLoadedImage()
    {
        EnsureConnected();

        if (_loadedImage == null)
        {
            _logger?.LogWarning("HtmlPrinter: No image loaded to print");
            return;
        }

        _logger?.LogInformation("HtmlPrinter: Printing loaded image");
        await RenderImageToHtml(_loadedImage, _loadedImageMaxWidth);

        _loadedImage.Dispose();
        _loadedImage = null;
    }

    /// <inheritdoc />
    public async Task PrintImageBitModeAsync(string imagePath, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.SingleDensity8Dot, int maxWidth = 10)
    {
        _logger?.LogInformation("HtmlPrinter: Printing image (bit mode) from file: {Path}", imagePath);

        if (!File.Exists(imagePath))
        {
            throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath);
        }

        using var image = await Image.LoadAsync<Rgba32>(imagePath);
        await PrintImageBitModeAsync(image, mode, maxWidth);
    }

    /// <inheritdoc />
    public async Task PrintImageBitModeAsync(Stream imageStream, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.DoubleDensity24Dot, int maxWidth = 384)
    {
        _logger?.LogInformation("HtmlPrinter: Printing image (bit mode) from stream");

        using var image = await Image.LoadAsync<Rgba32>(imageStream);
        await PrintImageBitModeAsync(image, mode, maxWidth);
    }

    /// <inheritdoc />
    public Task PrintImageBitModeAsync(Image<Rgba32> image, EpsonCommands.BitImageMode mode, int maxWidth)
    {
        EnsureConnected();
        _logger?.LogInformation("HtmlPrinter: Printing image (bit mode) {Width}x{Height}", image.Width, image.Height);

        return RenderImageToHtml(image, maxWidth);
    }

    /// <inheritdoc />
    public Task SendRawCommandAsync(byte[] command)
    {
        _logger?.LogDebug("HtmlPrinter: SendRawCommandAsync ignored ({Length} bytes)", command.Length);
        // Raw commands are ignored in HTML output
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetAbsolutePrintPosition(int x)
    {
        if (x < 0 || x > 65535)
            throw new ArgumentOutOfRangeException(nameof(x), "Position must be between 0 and 65535");

        _logger?.LogInformation("HtmlPrinter: Setting absolute print position to {X}", x);
        _absolutePosition = x;
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetDefaultLineSpacing()
    {
        _logger?.LogInformation("HtmlPrinter: Setting default line spacing");
        _lineSpacing = 14; // Default (scaled for screen)
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SetCustomLineSpacing(int spacing = 24)
    {
        if (spacing < 0 || spacing > 255)
            throw new ArgumentOutOfRangeException(nameof(spacing), "Spacing must be between 0 and 255");

        _logger?.LogInformation("HtmlPrinter: Setting custom line spacing to {Spacing}", spacing);
        // Convert dot spacing to approximate pixel spacing (scaled ~0.5x for screen)
        _lineSpacing = (int)(spacing * 0.6);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public ValueTask DisposeAsync()
    {
        Disconnect();
        return ValueTask.CompletedTask;
    }

    #region Private Methods

    private void ResetStylingState()
    {
        _currentFont = EpsonCommands.PrinterFont.A;
        _fontWidthMultiplier = 1;
        _fontHeightMultiplier = 1;
        _isEmphasized = false;
        _isRedColor = false;
        _isQuadrupleMode = false;
        _isBiggerFontWidthTM220 = false;
        _isBiggerFontHeightTM220 = false;
        _lineSpacing = 14;
        _absolutePosition = 0;
    }

    private string BuildCurrentStyle()
    {
        var styles = new List<string>();

        // Base font size from selected font
        var (baseWidth, baseHeight) = FontSizes[_currentFont];

        // Apply multipliers
        float effectiveWidthMultiplier = _fontWidthMultiplier;
        float effectiveHeightMultiplier = _fontHeightMultiplier;

        if (_isQuadrupleMode)
        {
            effectiveWidthMultiplier *= 2;
            effectiveHeightMultiplier *= 2;
        }

        if (_isBiggerFontWidthTM220)
        {
            effectiveWidthMultiplier *= 2f;
        }

        if (_isBiggerFontHeightTM220)
        {
            effectiveHeightMultiplier *= 1.2f;
        }

        int fontSize = (int)(baseHeight * effectiveHeightMultiplier);
        styles.Add($"font-size: {fontSize}px");

        // Letter spacing for width scaling (approximate)
        if (effectiveWidthMultiplier > 1)
        {
            int letterSpacing = (int)((effectiveWidthMultiplier) * baseWidth / 2);
            styles.Add($"letter-spacing: {letterSpacing}px");
        }

        // Font weight
        if (_isEmphasized)
        {
            styles.Add("font-weight: bold");
        }

        // Color
        if (_isRedColor)
        {
            styles.Add("color: #cc0000");
        }

        // Line height based on current line spacing
        styles.Add($"line-height: {_lineSpacing}px");

        return string.Join("; ", styles) + ";";
    }

    private async Task RenderImageToHtml(Image<Rgba32> sourceImage, int maxWidth)
    {
        // Clone and process the image (apply dithering like real printer)
        using var processedImage = sourceImage.Clone();

        // Resize if needed
        if (processedImage.Width > maxWidth)
        {
            EpsonImageConverter.ResizeImage(processedImage, maxWidth);
        }

        // Convert to grayscale
        using var grayscaleImage = EpsonImageConverter.ConvertToGrayscale(processedImage);

        // Apply Floyd-Steinberg dithering
        EpsonImageConverter.ApplyFloydSteinbergDithering(grayscaleImage);

        // Convert back to RGBA for saving
        using var ditheredImage = new Image<Rgba32>(grayscaleImage.Width, grayscaleImage.Height);
        grayscaleImage.ProcessPixelRows(ditheredImage, (sourceAccessor, targetAccessor) =>
        {
            for (int y = 0; y < sourceAccessor.Height; y++)
            {
                var sourceRow = sourceAccessor.GetRowSpan(y);
                var targetRow = targetAccessor.GetRowSpan(y);

                for (int x = 0; x < sourceAccessor.Width; x++)
                {
                    byte value = sourceRow[x].PackedValue;
                    targetRow[x] = new Rgba32(value, value, value, 255);
                }
            }
        });

        // Save image to file
        _imageCounter++;
        var imageName = $"image_{_imageCounter:D4}.png";
        var imagePath = Path.Combine(_imageDirectory, imageName);

        await ditheredImage.SaveAsync(imagePath, new PngEncoder());

        // Get relative path for HTML
        var htmlDir = Path.GetDirectoryName(_outputPath) ?? ".";
        if(htmlDir=="")
            htmlDir=".";
        var relativeImagePath = Path.GetRelativePath(htmlDir, imagePath).Replace('\\', '/');

        // Add to HTML content
        var positionStyle = _absolutePosition > 0 ? $"margin-left: {_absolutePosition}px;" : "";
        _content.AppendLine($"<div class=\"image\" style=\"{positionStyle}\"><img src=\"{relativeImagePath}\" alt=\"Printed image\"></div>");

        // Reset absolute position
        _absolutePosition = 0;

        WriteHtmlFile();

        _logger?.LogInformation("HtmlPrinter: Image saved to {Path}", imagePath);
    }

    private void WriteHtmlFile()
    {
        var html = GenerateHtml();
        File.WriteAllText(_outputPath, html, Encoding.UTF8);
        _logger?.LogDebug("HtmlPrinter: HTML file written to {Path}", _outputPath);
    }

    private string GenerateHtml()
    {
        return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
    <meta charset=""UTF-8"">
    <meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
    <title>Epson Printer Output</title>
    <style>
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }}

        body {{
            background-color: #f0f0f0;
            padding: 20px;
            font-family: 'Courier New', Courier, monospace;
        }}

        .receipt {{
            width: {_paperWidth}px;
            background-color: #fff;
            padding: 10px;
            margin: 0 auto;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            white-space: pre-wrap;
            word-wrap: break-word;
            overflow-wrap: break-word;
        }}

        .receipt span {{
            display: inline;
        }}

        .receipt .image {{
            display: block;
            margin: 5px 0;
        }}

        .receipt .image img {{
            max-width: 100%;
            height: auto;
            display: block;
        }}

        .cut {{
            position: relative;
            border-top: 2px dashed #333;
            margin: 20px 0;
            height: 0;
        }}

        .cut .scissors {{
            position: absolute;
            left: -5px;
            top: -12px;
            font-size: 18px;
            background: #fff;
            padding: 0 3px;
        }}

        /* Simulated paper texture */
        .receipt::before {{
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: repeating-linear-gradient(
                0deg,
                transparent,
                transparent 1px,
                rgba(0,0,0,0.02) 1px,
                rgba(0,0,0,0.02) 2px
            );
            pointer-events: none;
        }}
    </style>
</head>
<body>
    <div class=""receipt"">
{_content}
    </div>
</body>
</html>";
    }

    private static string EscapeHtml(string text)
    {
        return text
            .Replace("&", "&amp;")
            .Replace("<", "&lt;")
            .Replace(">", "&gt;")
            .Replace("\"", "&quot;")
            .Replace("'", "&#39;");
    }

    private void EnsureConnected()
    {
        if (!_isConnected)
        {
            throw new EpsonConnectionException("Not connected. Call ConnectAsync first.");
        }
    }

    #endregion
}
