using Microsoft.VisualBasic.Logging;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace eSuite.WorkBench.Services
{
    public class CommandsService : ICommandsService
    {
        private static readonly List<string> containerNames = new() { "esuite.Database.Migrator", "esuite.API", "esuite.WebUI", "esuite.messageprocessor", "esuite.scheduler" };

        public CommandsService()
        {
            var p = new Process
            {
                StartInfo =
                {
                    FileName = "docker",
                    Arguments = BuildLoginCommand(),
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                }
            };

            p.Start();
            p.WaitForExit();
        }

        public event EventHandler<FeedbackEventArgs> FeedbackMessage;
        protected void DoFeedbackMessage(string message)
        {
            if (FeedbackMessage != null)
            {
                var feedbackEventArgs = new FeedbackEventArgs
                {
                    Message = message
                };


                FeedbackMessage(this, feedbackEventArgs);
            }
        }

        private static async Task PullImageAsync(string imageName, string tag)
        {
            await RunCommandAsync("docker", $"pull {imageName}:{tag}", createWindow:true, useShellExecute:true);
        }

        public async Task StartProxyContainerAsync(string tag)
        {
            if (string.IsNullOrWhiteSpace(tag))
                return;

            DoFeedbackMessage("Starting Proxy container");
            await PullImageAsync("esuite.azurecr.io/e-suite.api", tag);
            await RunCommandAsync("docker", $"run --name esuite.Proxy -d -p3001:80 --env SERVER_NAME=localhost --env API_URL=http://host.docker.internal:7066 --env WEBUI_URL=http://host.docker.internal:3000 esuite.azurecr.io/e-suite.proxy:{tag}");
            DoFeedbackMessage("Started API container");
        }

        public async Task StartApiContainerAsync(string tag, string databaseName)
        {
            if (string.IsNullOrWhiteSpace(tag))
                return;

            DoFeedbackMessage("Starting API container");
            await PullImageAsync("esuite.azurecr.io/e-suite.api", tag);
            await RunCommandAsync("docker", $"run --name esuite.API -d -p7066:80 --env BASE_URL=http://localhost:3001/  --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER=esuite_{databaseName}ApplicationUser --env MAIL_SERVER=host.docker.internal --env RABBITMQ_HOSTNAME=host.docker.internal esuite.azurecr.io/e-suite.api:{tag}");
            DoFeedbackMessage("Started API container");
        }

        public async Task StartWebUiContainerAsync(string tag)
        {
            if (string.IsNullOrWhiteSpace(tag))
                return;

            DoFeedbackMessage("Starting WebUI container");
            await PullImageAsync("esuite.azurecr.io/e-suite.webui", tag);
            await RunCommandAsync("docker", $"run --name esuite.WebUI -d -p3000:80 --env NODE_ENV=production --env API_URL=http://localhost:3001/api esuite.azurecr.io/e-suite.webui:{tag}");
            DoFeedbackMessage("Started WebUI container");
        }

        public async Task StartDatabaseMigratorContainerAsync(string tag, string databaseName)
        {
            if (string.IsNullOrWhiteSpace(tag))
                return;

            DoFeedbackMessage("Starting database migrator container");
            await PullImageAsync("esuite.azurecr.io/esuite.database.migrator", tag);
            await RunCommandAsync("docker", $"run --name esuite.Database.Migrator --rm --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER={Properties.Settings.Default.SqlServerAdminLogin} --env SQL_PASSWORD={Properties.Settings.Default.SqlServerAdminPassword} --env SQL_APPLICATION_USER=esuite_{databaseName}ApplicationUser esuite.azurecr.io/e-suite.database.migrator:{tag}");
            DoFeedbackMessage("Started database migrator container");
        }

        public async Task StartRabbitMQContainerAsync()
        {
            DoFeedbackMessage("Starting RabbitMQ");
            await PullImageAsync("rabbitmq", "management");
            await RunCommandAsync("docker", $"run --name RabbitMQ -d -p8080:15672 -p5672:5672 --restart always rabbitmq:management");
            DoFeedbackMessage("Started RabbitMQ");
        }

        public async Task StartSchedulerContainerAsync(string tag, string databaseName)
        {
            if (string.IsNullOrWhiteSpace(tag))
                return;

            DoFeedbackMessage("Starting e-suite.scheduler");
            await PullImageAsync("esuite.azurecr.io/e-suite.scheduler", tag);
            await RunCommandAsync("docker", $"run --name esuite.scheduler -d --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER=esuite_{databaseName}ApplicationUser --env RABBITMQ_HOSTNAME=host.docker.internal esuite.azurecr.io/e-suite.scheduler:{tag}");
            DoFeedbackMessage("Started e-suite.scheduler");
        }

        public async Task StartMessageProcessorContainerAsync(string tag, string databaseName)
        {
            if (string.IsNullOrWhiteSpace(tag))
                return;

            DoFeedbackMessage("Starting esuite.messageprocessor");
            await PullImageAsync("esuite.azurecr.io/e-suite.messageprocessor", tag);
            await RunCommandAsync("docker", $"run --name esuite.messageprocessor -d --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER=esuite_{databaseName}ApplicationUser --env RABBITMQ_HOSTNAME=host.docker.internal esuite.azurecr.io/e-suite.messageprocessor:{tag}");
            DoFeedbackMessage("Started esuite.messageprocessor");
        }

        public async Task<bool> IsAnyContainerRunningAsync()
        {
            var runningContainers = (await RunCommandAsync("docker", "container ls -a --format \"{{.Names}}\"")).StdOut;
            return containerNames.Any(x => runningContainers.Contains(x));
        }

        public bool IsAnyContainerRunning()
        {
            var startInfo = new ProcessStartInfo
            {
                FileName = "docker",
                Arguments = "container ls -a --format \"{{.Names}}\"",
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            };

            var process = Process.Start(startInfo);
            var outputBuilder = new StringBuilder();

            process.OutputDataReceived += (sender, e) =>
            {
                if (e.Data == null)
                {
                    return;
                }

                outputBuilder.AppendLine(e.Data);
            };

            process.BeginOutputReadLine();
            process.WaitForExit();

            var runningContainers = outputBuilder.ToString();
            return containerNames.Any(x => runningContainers.Contains(x));
        }

        public async Task ConnectToContainerRegistry()
        {
            var loginCommand =
                BuildLoginCommand();
            await RunCommandAsync("docker", loginCommand);
        }

        private static string BuildLoginCommand()
        {
            return $"login -u {ConfigurationManager.AppSettings["AzureContainerRegistryUserName"]} -p {ConfigurationManager.AppSettings["AzureContainerRegistryPassword"]} esuite.azurecr.io";
        }

        public async Task LaunchSwagger()
        {
            await RunCommandAsync("http://localhost:3001/swagger", useShellExecute: true, launchAndForget: true);
        }

        public async Task LaunchWebUi()
        {
            await RunCommandAsync("http://localhost:3001/", useShellExecute: true, launchAndForget: true);
        }

        public async Task LaunchHealthZ()
        {
            await RunCommandAsync("http://localhost:3001/healthz", useShellExecute: true, launchAndForget: true);
        }

        public async Task StopContainersAsync()
        {
            //Note RabbitMQ is left running, this is to make it easy for developers not to have to run the command.
            await RunCommandAsync("docker", "stop -t 2 esuite.API esuite.WebUI esuite.scheduler esuite.messageprocessor esuite.Proxy RabbitMQ");
            await RunCommandAsync("docker", "rm esuite.API esuite.WebUI esuite.scheduler esuite.messageprocessor esuite.Proxy RabbitMQ");
        }

        private static async Task<RunCommandResult> RunCommandAsync(string command, string arguments = null, TimeSpan? timeout = null, bool createWindow = false, bool useShellExecute = false, bool launchAndForget = false)
        {
            if (timeout == null)
            {
                timeout = TimeSpan.FromSeconds(300);
            }

            var startInfo = new ProcessStartInfo
            {
                Arguments = arguments ?? string.Empty,
                CreateNoWindow = !createWindow,
                FileName = command,
                RedirectStandardError = !useShellExecute,
                RedirectStandardOutput = !useShellExecute,
                UseShellExecute = useShellExecute
            };

            var process = Process.Start(startInfo);
            var errorBuilder = new StringBuilder();
            var outputBuilder = new StringBuilder();

            process.ErrorDataReceived += (sender, e) =>
            {
                if (e.Data == null)
                {
                    return;
                }

                lock (errorBuilder)
                {
                    errorBuilder.AppendLine(e.Data);
                }
            };

            process.OutputDataReceived += (sender, e) =>
            {
                if (e.Data == null)
                {
                    return;
                }

                lock (outputBuilder)
                {
                    outputBuilder.AppendLine(e.Data);
                }
            };

            if (!useShellExecute)
            {
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
            }

            if (!launchAndForget)
            {
                await Task.Run(() => process.WaitForExit((int)timeout.Value.TotalMilliseconds));

                return new RunCommandResult
                {
                    ExitCode = process.ExitCode,
                    StdErr = errorBuilder.ToString(),
                    StdOut = outputBuilder.ToString()
                };
            }

            return new RunCommandResult
            {
                ExitCode = 0,
                StdErr = string.Empty,
                StdOut = string.Empty
            };

        }

        private class RunCommandResult
        {
            public int ExitCode { get; set; }

            public string StdErr { get; set; }

            public string StdOut { get; set; }

            public bool Success => ExitCode == 0;
        }
    }
}
