using System.Data.OleDb;
using System.Text;
using System.Text.Json;
using System.Threading.RateLimiting;
using Download_Faturas;
using iText.Layout.Splitting;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Webhook_4docs
{
    public class Program
    {
        private static readonly RateLimiter connRateLimiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions
        {
            PermitLimit = 4
        });
        public static void Main(string[] args)
        {
            static string IndexedFilename(string stub, string extension)
            {
                int ix = 0;
                string? filename = null;

                if (File.Exists(String.Format("{0}.{1}", stub, extension)))
                {
                    do
                    {
                        ix++;
                        filename = String.Format("{0} ({1}).{2}", stub, ix, extension);
                    } while (File.Exists(filename));
                }
                else
                {
                    filename = String.Format("{0}.{1}", stub, extension);
                }
                return filename;
            }

            var builder = WebApplication.CreateBuilder(args);
            builder.Configuration.AddJsonFile("appsettings.json");

            var connectionString = builder.Configuration.GetConnectionString("WebhookDbContext");
            string? appFolder = builder.Configuration["PathBase"];

            builder.Services.AddDbContext<WebhookDbContext>(options =>
                options.UseNpgsql(connectionString)
            );

            builder.Services.AddLogging(loggingBuilder =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder.AddConsole();
            });

            // Add services to the container.
            builder.Services.AddRazorPages();

            var app = builder.Build();

            var logger = app.Services.GetRequiredService<ILogger<Program>>();

            using (var scope = app.Services.CreateScope())
            {
                var dbContext = scope.ServiceProvider.GetRequiredService<WebhookDbContext>();
                dbContext.Database.EnsureCreated();
                dbContext.Database.Migrate();
            }

            string fatura_arquivo;
            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            
            app.UsePathBase(appFolder);

            app.UseRouting();

            app.UseStaticFiles();

            app.UseAuthorization();

            app.MapRazorPages();

            app.Use((context, next) =>
            {
                context.Request.PathBase = new PathString(appFolder);
                return next(context);
            });

            // Endpoint para \api
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapPatch("/api", async context =>
                {
                    
                    string requestBody;
                    using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true))
                    {
                        requestBody = await reader.ReadToEndAsync();
                    }

                    var JsonBody = JsonDocument.Parse(requestBody).RootElement;
                    string CaminhoDB = "X:/Middle/Informativo Setorial/Modelo Word/BD1_dados cadastrais e faturas.accdb";

                    if (JsonBody.TryGetProperty("requestID", out JsonElement fatura_ID_json))
                    {
                        string fatura_ID = fatura_ID_json.ToString();

                        if (!JsonBody.TryGetProperty("json", out JsonElement root)) { return; }

                        JsonElement DadosJson = JsonDocument.Parse(root.ToString()).RootElement;

                        if (root.Get("documentType")?.ToString().ToLower() != "nota_fiscal") { return; }

                        Fatura fatura = new(fatura_ID, JsonBody);
                        bool completed = false;

                        while (!completed)
                        {
                            var connLease = await connRateLimiter.AcquireAsync();

                            if (connLease.IsAcquired)
                            {
                                using (OleDbConnection conn = new(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21"))
                                {
                                    if (conn != null)
                                    {
                                        if (conn.State == System.Data.ConnectionState.Closed)
                                        {
                                            await conn.OpenAsync();
                                        }

                                        try
                                        {
                                            fatura.Processar(conn);
                                        }
                                        catch (Exception ex)
                                        {
                                            completed = true;
                                            connLease.Dispose();
                                            throw new Exception(ex.Message);
                                        }
                                    }
                                }
                                completed = true;
                                connLease.Dispose();
                            }
                        }

                        string? status = fatura.Status;

                        int status_id = 0;

                        switch (status)
                        {
                            case ("FATURA DUPLICADA NO BD"):
                                status_id = 4;
                                break;
                            case ("UNIDADE CONSUMIDORA NÃO LOCALIZADA NO BD"):
                                status_id = 5;
                                break;
                            case ("FATURA INCLUIDA NO BD"):
                                status_id = 6;
                                break;
                            case (_):
                                status = "ERRO";
                                status_id = 7;
                                break;
                        }

                        try
                        {
                            string path = $@"X:\Middle\Carteira {fatura.Gestao![0]}\Carteira {fatura.Gestao}\Faturas fourdocs\{status_id} - {status}";
                            if (status_id == 6 && fatura.PastaTUSD!.Exists)
                            {
                                path = fatura.PastaTUSD!.FullName;
                            }
                            fatura_arquivo = IndexedFilename($@"{path}\ID {fatura_ID!} -  Mês {fatura.Mes} -  Empresa {fatura.Empresa} -  Unidade {fatura.Unidade}", "pdf");
                            CriarArquivo(fatura_arquivo, JsonBody.GetProperty("pdfFile").ToString());

                            var DatabaseModel = new ProcessedInvoices
                            {
                                InvoiceId = Int32.Parse(fatura_ID),
                                DateTimeProcessed = DateTime.UtcNow,
                                Status = status,
                                InvoicePath = fatura_arquivo
                            };

                            logger.LogInformation("Fatura incluída no BD");

                            using (var scope = app.Services.CreateScope())
                            {
                                var dbContext = scope.ServiceProvider.GetRequiredService<WebhookDbContext>();
                                dbContext.ProcessedInvoices.Add(DatabaseModel);
                                await dbContext.SaveChangesAsync();
                            }

                            logger.LogInformation("Fatura salva na pasta " + fatura_arquivo);
                        }
                        catch
                        {
                            logger.LogError("Erro no processamento da fatura");
                        }
                        
                    }
                    else if (JsonBody.TryGetProperty("healthy", out _) && JsonBody.TryGetProperty("blame", out _) && JsonBody.TryGetProperty("locationID", out _))
                    {
                        double errorID = 0;

                        switch (JsonBody.GetProperty("healthy").GetBoolean(), JsonBody.GetProperty("blame").ToString().ToLower())
                        {
                            case (false, "user"):
                                logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Sem acesso");
                                errorID = 1;
                                break;
                            case (false, "system"):
                                logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Com erro de sistema");
                                errorID = 2;
                                break;
                            case (false, _):
                                logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Com erro " + JsonBody.GetProperty("blame").ToString());
                                errorID = 3;
                                break;
                            case (true, _):
                                //logger.LogInformation("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " está saudável");
                                break;
                        }

                        int test = await UpdateErrorIdStatusAsync(CaminhoDB, JsonBody.GetProperty("locationID").GetInt64(), errorID, (JsonBody.GetProperty("systemText").ToString() == "" ? JsonBody.GetProperty("websiteText").ToString() : JsonBody.GetProperty("systemText").ToString()), logger);

                        if (test == 0)
                        {
                            await InsertErrorIdStatusAsync(CaminhoDB, errorID, requestBody, logger);
                        }
                    }
                });
            });
            app.Run();
        }
        public static async Task InsertErrorIdStatusAsync(string CaminhoDB, double errorID, string requestBody, ILogger logger)
        {
            var JsonBody = JsonDocument.Parse(requestBody).RootElement;

            // Extração dos valores do JSON
            double locationID = JsonBody.GetProperty("locationID").GetInt64();
            string accountID = JsonBody.GetProperty("accountID").ToString();
            string deliveryTimeStamp = JsonBody.GetProperty("deliveryTimestamp").ToString();
            string provider = JsonBody.GetProperty("provider").ToString();
            string accessPoint = JsonBody.GetProperty("accessPoint").ToString();
            string slaStatus = JsonBody.GetProperty("slaStatus").ToString();
            string healthy = JsonBody.GetProperty("healthy").ToString();
            string blame = JsonBody.GetProperty("blame").ToString();
            string lastSuccess = JsonBody.GetProperty("lastSuccess").ToString();
            string active = JsonBody.GetProperty("active").ToString();
            string blacklistStatus = JsonBody.GetProperty("blacklistStatus").ToString();
            string lastActivated = JsonBody.GetProperty("lastActivated").ToString();
            string lastDeactivated = JsonBody.GetProperty("lastDeactivated").ToString();
            string lastDeactivatedBy = JsonBody.GetProperty("lastDeactivatedBy").ToString();

            int maxRetries = 3;
            int attempt = 0;

            while (attempt < maxRetries)
            {
                var connLease = await connRateLimiter.AcquireAsync();

                if (connLease.IsAcquired)
                {
                    try
                    {
                        using (OleDbConnection conn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21"))
                        {
                            await conn.OpenAsync();

                            using (OleDbCommand cmd = new OleDbCommand(
                                @"INSERT INTO AgVirtual4DocsErros (
                            locationID, accountID, errorID, deliveryTimeStamp, provider, accessPoint, slaStatus, 
                            healthy, blame, lastSuccess, active, blacklistStatus, lastActivated, lastDeactivated, lastDeactivatedBy
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", conn))
                            {
                                // Adiciona parâmetros de forma segura
                                cmd.Parameters.AddWithValue("@locationID", locationID);
                                cmd.Parameters.AddWithValue("@accountID", accountID);
                                cmd.Parameters.AddWithValue("@errorID", errorID);
                                cmd.Parameters.AddWithValue("@deliveryTimeStamp", deliveryTimeStamp);
                                cmd.Parameters.AddWithValue("@provider", provider);
                                cmd.Parameters.AddWithValue("@accessPoint", accessPoint);
                                cmd.Parameters.AddWithValue("@slaStatus", slaStatus);
                                cmd.Parameters.AddWithValue("@healthy", healthy);
                                cmd.Parameters.AddWithValue("@blame", blame);
                                cmd.Parameters.AddWithValue("@lastSuccess", lastSuccess);
                                cmd.Parameters.AddWithValue("@active", active);
                                cmd.Parameters.AddWithValue("@blacklistStatus", blacklistStatus);
                                cmd.Parameters.AddWithValue("@lastActivated", lastActivated);
                                cmd.Parameters.AddWithValue("@lastDeactivated", lastDeactivated);
                                cmd.Parameters.AddWithValue("@lastDeactivatedBy", lastDeactivatedBy);

                                // Executa o comando
                                await cmd.ExecuteNonQueryAsync();
                            }
                        }

                        break; // Sai do loop em caso de sucesso
                    }
                    catch (OleDbException ex)
                    {
                        // Registra o erro
                        logger.LogInformation($"Erro no OleDb insert: {ex.Message} (Tentativa {attempt + 1} de {maxRetries})");

                        if (attempt < maxRetries - 1)
                        {
                            // Aguarda antes de tentar novamente
                            await Task.Delay(1000 * (int)Math.Pow(2, attempt)); // Retry com Backoff Exponencial
                        }
                        else
                        {
                            // Propaga a exceção na última tentativa
                            throw;
                        }
                    }
                    finally
                    {
                        connLease.Dispose();
                    }

                    attempt++; // Incrementa a tentativa após adquirir o lease, mesmo que falhe
                }
                else
                {
                    // Aguarda um curto período antes de tentar novamente se não conseguiu adquirir o lease
                    await Task.Delay(200);
                }
            }
        }
        public static async Task<int> UpdateErrorIdStatusAsync(string CaminhoDB, double location_id, double errorID, string systemText, ILogger logger)
        {
            int test = 0;
            int maxRetries = 3;
            int attempt = 0;

            while (attempt < maxRetries)
            {
                var connLease = await connRateLimiter.AcquireAsync();

                if (connLease.IsAcquired)
                {
                    try
                    {
                        using (OleDbConnection conn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21"))
                        {
                            await conn.OpenAsync();

                            using (OleDbCommand cmd = new OleDbCommand(
                                @"UPDATE AgVirtual4Docs 
                                  SET errorID = @errorID, status = @status
                                  WHERE location_id = @location_id", conn))
                            {
                                // Adiciona parâmetros de forma segura
                                cmd.Parameters.AddWithValue("@errorID", errorID);
                                cmd.Parameters.AddWithValue("@status", systemText);
                                cmd.Parameters.AddWithValue("@location_id", location_id);

                                // Executa o comando e captura o resultado
                                test = await cmd.ExecuteNonQueryAsync();
                            }
                        }

                        break; // Sai do loop em caso de sucesso
                    }
                    catch (OleDbException ex)
                    {
                        // Registra o erro
                        logger.LogInformation($"Erro no OleDb update: {ex.Message} (Tentativa {attempt + 1} de {maxRetries})");

                        if (attempt < maxRetries - 1)
                        {
                            // Aguarda antes de tentar novamente
                            await Task.Delay(1000 * (int)Math.Pow(2, attempt)); // Retry com Backoff Exponencial
                        }
                        else
                        {
                            // Propaga a exceção na última tentativa
                            throw;
                        }
                    }
                    finally
                    {
                        connLease.Dispose();
                    }

                    attempt++; // Incrementa a tentativa após adquirir o lease, mesmo que falhe
                }
                else
                {
                    // Aguarda um curto período antes de tentar novamente se não conseguiu adquirir o lease
                    await Task.Delay(200);
                }
            }

            return test;
        }
        public static void CriarArquivo(string fatura_arquivo, string pdfFile64)
        {
            //string fatura_arquivo = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test.pdf";
            if (!File.Exists(fatura_arquivo))
            {
                byte[] bytes = Convert.FromBase64String(pdfFile64);

                System.IO.FileStream stream = new(fatura_arquivo, FileMode.CreateNew);
                System.IO.BinaryWriter writer =
                    new(stream);
                writer.Write(bytes, 0, bytes.Length);
                writer.Close();
            }
        }
    }
    public static partial class JsonExtensions
    {
        public static JsonElement? Get(this JsonElement element, string name) =>
            element.ValueKind != JsonValueKind.Null && element.ValueKind != JsonValueKind.Undefined && element.TryGetProperty(name, out var value)
                ? value : (JsonElement?)null;

        public static JsonElement? Get(this JsonElement element, int index)
        {
            if (element.ValueKind == JsonValueKind.Null || element.ValueKind == JsonValueKind.Undefined)
                return null;
            // Throw if index < 0
            return index < element.GetArrayLength() ? element[index] : null;
        }
    }
}