// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Maui.Storage;
using System.Diagnostics;
using System.Text;

namespace Microsoft.Maui.Platform.Linux.Services;

/// <summary>
/// File picker service using xdg-desktop-portal for native dialogs.
/// Falls back to zenity/kdialog if portal is unavailable.
/// </summary>
public class PortalFilePickerService : IFilePicker
{
    private bool _portalAvailable = true;
    private string? _fallbackTool;

    public PortalFilePickerService()
    {
        DetectAvailableTools();
    }

    private void DetectAvailableTools()
    {
        // Check if portal is available
        _portalAvailable = CheckPortalAvailable();

        if (!_portalAvailable)
        {
            // Check for fallback tools
            if (IsCommandAvailable("zenity"))
                _fallbackTool = "zenity";
            else if (IsCommandAvailable("kdialog"))
                _fallbackTool = "kdialog";
            else if (IsCommandAvailable("yad"))
                _fallbackTool = "yad";
        }
    }

    private bool CheckPortalAvailable()
    {
        try
        {
            // Check if xdg-desktop-portal is running
            var output = RunCommand("busctl", "--user list | grep -q org.freedesktop.portal.Desktop && echo yes");
            return output.Trim() == "yes";
        }
        catch
        {
            return false;
        }
    }

    private bool IsCommandAvailable(string command)
    {
        try
        {
            var output = RunCommand("which", command);
            return !string.IsNullOrWhiteSpace(output);
        }
        catch
        {
            return false;
        }
    }

    public async Task<FileResult?> PickAsync(PickOptions? options = null)
    {
        options ??= new PickOptions();
        var results = await PickFilesAsync(options, allowMultiple: false);
        return results.FirstOrDefault();
    }

    public async Task<IEnumerable<FileResult>> PickMultipleAsync(PickOptions? options = null)
    {
        options ??= new PickOptions();
        return await PickFilesAsync(options, allowMultiple: true);
    }

    private async Task<IEnumerable<FileResult>> PickFilesAsync(PickOptions options, bool allowMultiple)
    {
        if (_portalAvailable)
        {
            return await PickWithPortalAsync(options, allowMultiple);
        }
        else if (_fallbackTool != null)
        {
            return await PickWithFallbackAsync(options, allowMultiple);
        }
        else
        {
            // No file picker available
            DiagnosticLog.Warn("PortalFilePickerService", "No file picker available (install xdg-desktop-portal, zenity, or kdialog)");
            return Enumerable.Empty<FileResult>();
        }
    }

    private async Task<IEnumerable<FileResult>> PickWithPortalAsync(PickOptions options, bool allowMultiple)
    {
        try
        {
            // Use gdbus to call the portal
            var filterArgs = BuildPortalFilterArgs(options.FileTypes);
            var multipleArg = allowMultiple ? "true" : "false";
            var title = options.PickerTitle ?? "Open File";

            // Build the D-Bus call
            var args = new StringBuilder();
            args.Append("call --session ");
            args.Append("--dest org.freedesktop.portal.Desktop ");
            args.Append("--object-path /org/freedesktop/portal/desktop ");
            args.Append("--method org.freedesktop.portal.FileChooser.OpenFile ");
            args.Append("\"\" "); // Parent window (empty for no parent)
            args.Append($"\"{EscapeForShell(title)}\" "); // Title

            // Options dictionary
            args.Append("@a{sv} {");
            args.Append($"'multiple': <{multipleArg}>");
            if (filterArgs != null)
            {
                args.Append($", 'filters': <{filterArgs}>");
            }
            args.Append("}");

            var output = await Task.Run(() => RunCommand("gdbus", args.ToString()));

            // Parse the response to get the request path
            // Response format: (objectpath '/org/freedesktop/portal/desktop/request/...',)
            var requestPath = ParseRequestPath(output);
            if (string.IsNullOrEmpty(requestPath))
            {
                return Enumerable.Empty<FileResult>();
            }

            // Wait for the response signal (simplified - in production use D-Bus signal subscription)
            await Task.Delay(100);

            // For now, fall back to synchronous zenity if portal response parsing is complex
            if (_fallbackTool != null)
            {
                return await PickWithFallbackAsync(options, allowMultiple);
            }

            return Enumerable.Empty<FileResult>();
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("PortalFilePickerService", $"Portal error: {ex.Message}");
            // Fall back to zenity/kdialog
            if (_fallbackTool != null)
            {
                return await PickWithFallbackAsync(options, allowMultiple);
            }
            return Enumerable.Empty<FileResult>();
        }
    }

    private async Task<IEnumerable<FileResult>> PickWithFallbackAsync(PickOptions options, bool allowMultiple)
    {
        return _fallbackTool switch
        {
            "zenity" => await PickWithZenityAsync(options, allowMultiple),
            "kdialog" => await PickWithKdialogAsync(options, allowMultiple),
            "yad" => await PickWithYadAsync(options, allowMultiple),
            _ => Enumerable.Empty<FileResult>()
        };
    }

    private async Task<IEnumerable<FileResult>> PickWithZenityAsync(PickOptions options, bool allowMultiple)
    {
        var args = new StringBuilder();
        args.Append("--file-selection ");

        if (!string.IsNullOrEmpty(options.PickerTitle))
        {
            args.Append($"--title=\"{EscapeForShell(options.PickerTitle)}\" ");
        }

        if (allowMultiple)
        {
            args.Append("--multiple --separator=\"|\" ");
        }

        // Add file filters from FilePickerFileType
        var extensions = GetExtensionsFromFileType(options.FileTypes);
        if (extensions.Count > 0)
        {
            var filterPattern = string.Join(" ", extensions.Select(e => $"*{e}"));
            args.Append($"--file-filter=\"Files | {filterPattern}\" ");
        }

        var output = await Task.Run(() => RunCommand("zenity", args.ToString()));

        if (string.IsNullOrWhiteSpace(output))
        {
            return Enumerable.Empty<FileResult>();
        }

        var files = output.Trim().Split('|', StringSplitOptions.RemoveEmptyEntries);
        return files.Select(f => new FileResult(f.Trim())).ToList();
    }

    private async Task<IEnumerable<FileResult>> PickWithKdialogAsync(PickOptions options, bool allowMultiple)
    {
        var args = new StringBuilder();
        args.Append("--getopenfilename ");

        // Start directory
        args.Append(". ");

        // Add file filters
        var extensions = GetExtensionsFromFileType(options.FileTypes);
        if (extensions.Count > 0)
        {
            var filterPattern = string.Join(" ", extensions.Select(e => $"*{e}"));
            args.Append($"\"Files ({filterPattern})\" ");
        }

        if (!string.IsNullOrEmpty(options.PickerTitle))
        {
            args.Append($"--title \"{EscapeForShell(options.PickerTitle)}\" ");
        }

        if (allowMultiple)
        {
            args.Append("--multiple --separate-output ");
        }

        var output = await Task.Run(() => RunCommand("kdialog", args.ToString()));

        if (string.IsNullOrWhiteSpace(output))
        {
            return Enumerable.Empty<FileResult>();
        }

        var files = output.Trim().Split('\n', StringSplitOptions.RemoveEmptyEntries);
        return files.Select(f => new FileResult(f.Trim())).ToList();
    }

    private async Task<IEnumerable<FileResult>> PickWithYadAsync(PickOptions options, bool allowMultiple)
    {
        // YAD is similar to zenity
        var args = new StringBuilder();
        args.Append("--file ");

        if (!string.IsNullOrEmpty(options.PickerTitle))
        {
            args.Append($"--title=\"{EscapeForShell(options.PickerTitle)}\" ");
        }

        if (allowMultiple)
        {
            args.Append("--multiple --separator=\"|\" ");
        }

        var extensions = GetExtensionsFromFileType(options.FileTypes);
        if (extensions.Count > 0)
        {
            var filterPattern = string.Join(" ", extensions.Select(e => $"*{e}"));
            args.Append($"--file-filter=\"Files | {filterPattern}\" ");
        }

        var output = await Task.Run(() => RunCommand("yad", args.ToString()));

        if (string.IsNullOrWhiteSpace(output))
        {
            return Enumerable.Empty<FileResult>();
        }

        var files = output.Trim().Split('|', StringSplitOptions.RemoveEmptyEntries);
        return files.Select(f => new FileResult(f.Trim())).ToList();
    }

    /// <summary>
    /// Extracts file extensions from a MAUI FilePickerFileType.
    /// </summary>
    private List<string> GetExtensionsFromFileType(FilePickerFileType? fileType)
    {
        var extensions = new List<string>();
        if (fileType == null) return extensions;

        try
        {
            // FilePickerFileType.Value is IEnumerable<string> for the current platform
            var value = fileType.Value;
            if (value == null) return extensions;

            foreach (var ext in value)
            {
                // Skip MIME types, only take file extensions
                if (ext.StartsWith(".") || (!ext.Contains('/') && !ext.Contains('*')))
                {
                    var normalized = ext.StartsWith(".") ? ext : $".{ext}";
                    if (!extensions.Contains(normalized))
                    {
                        extensions.Add(normalized);
                    }
                }
            }
        }
        catch
        {
            // Silently fail if we can't parse the file type
        }

        return extensions;
    }

    private string? BuildPortalFilterArgs(FilePickerFileType? fileType)
    {
        var extensions = GetExtensionsFromFileType(fileType);
        if (extensions.Count == 0)
            return null;

        var patterns = string.Join(", ", extensions.Select(e => $"(uint32 0, '*{e}')"));
        return $"[('Files', [{patterns}])]";
    }

    private string? ParseRequestPath(string output)
    {
        // Parse D-Bus response like: (objectpath '/org/freedesktop/portal/desktop/request/...',)
        var start = output.IndexOf("'/");
        var end = output.IndexOf("',", start);
        if (start >= 0 && end > start)
        {
            return output.Substring(start + 1, end - start - 1);
        }
        return null;
    }

    private string EscapeForShell(string input)
    {
        return input.Replace("\"", "\\\"").Replace("'", "\\'");
    }

    private string RunCommand(string command, string arguments)
    {
        try
        {
            using var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = command,
                    Arguments = arguments,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };

            process.Start();
            var output = process.StandardOutput.ReadToEnd();
            process.WaitForExit(30000);
            return output;
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("PortalFilePickerService", $"Command error: {ex.Message}");
            return "";
        }
    }
}
