using System.Text.Json;
using Duplicati.Library.RestAPI;
using Duplicati.Server.Database;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Exceptions;
using Microsoft.AspNetCore.Mvc;

namespace Duplicati.WebserverCore.Endpoints.V1;

public class Backups : IEndpointV1
{
    public static void Map(RouteGroupBuilder group)
    {
        group.MapGet("/backups", ([FromServices] Connection connection)
            => ExecuteGet(connection))
               .RequireAuthorization();

        // TODO: Figure out why the JSON deserialization is not working here
        // group.MapPost("/backups", ([FromServices] Connection connection, [FromBody] Dto.BackupAndScheduleInputDto input, [FromQuery] bool? temporary, [FromQuery] bool? existingdb)
        //     => ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false)).RequireAuthorization();

        group.MapPost("/backups", async ([FromServices] Connection connection, [FromQuery] bool? temporary, [FromQuery] bool? existingdb, [FromServices] IHttpContextAccessor httpContextAccessor) =>
            {
                var opts = new JsonSerializerOptions()
                {
                    Converters = { new DayOfWeekStringEnumConverter() }
                };
                var input = await JsonSerializer.DeserializeAsync<Dto.BackupAndScheduleInputDto>(httpContextAccessor.HttpContext!.Request.Body, opts)
                    ?? throw new BadRequestException("No data found in request body");
                return ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false);
            })
            .RequireAuthorization();

        group.MapPost("/backups/import", ([FromBody] Dto.ImportBackupInputDto input, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor) =>
        {
            using var tempfile = new Library.Utility.TempFile();
            File.WriteAllBytes(tempfile, Convert.FromBase64String(input.config));

            var html = ExecuteImport(connection, input.cmdline ?? false, input.import_metadata ?? false, input.direct ?? false, input.passphrase ?? "", tempfile);
            httpContextAccessor.HttpContext!.Response.ContentType = "text/html";
            return html;

        }).RequireAuthorization();
    }

    private static IEnumerable<Dto.BackupAndScheduleOutputDto> ExecuteGet(Connection connection)
    {
        var schedules = connection.Schedules;
        var backups = connection.Backups;

        var all = backups.Select(n => new
        {
            IsUnencryptedOrPassphraseStored = connection.IsUnencryptedOrPassphraseStored(long.Parse(n.ID)),
            Backup = n,
            Schedule = schedules.FirstOrDefault(x => x.Tags != null && x.Tags.Contains("ID=" + n.ID))
        });

        return all.Select(x => new Dto.BackupAndScheduleOutputDto()
        {
            Backup = new Dto.BackupDto()
            {
                ID = x.Backup.ID,
                Name = x.Backup.Name,
                Description = x.Backup.Description,
                IsTemporary = x.Backup.IsTemporary,
                IsUnencryptedOrPassphraseStored = x.IsUnencryptedOrPassphraseStored,
                Metadata = x.Backup.Metadata,
                Sources = x.Backup.Sources,
                Settings = x.Backup.Settings?.Select(y => new Dto.SettingDto()
                {
                    Name = y.Name,
                    Value = y.Value,
                    Filter = y.Filter,
                    Argument = y.Argument
                }),
                Filters = x.Backup.Filters?.Select(y => new Dto.FilterDto()
                {
                    Order = y.Order,
                    Include = y.Include,
                    Expression = y.Expression,
                }),
                TargetURL = x.Backup.TargetURL,
                DBPath = x.Backup.DBPath,
                Tags = x.Backup.Tags
            },
            Schedule = x.Schedule == null ? null : new Dto.ScheduleDto()
            {
                ID = x.Schedule.ID,
                Tags = x.Schedule.Tags,
                Time = x.Schedule.Time,
                Repeat = x.Schedule.Repeat,
                LastRun = x.Schedule.LastRun,
                Rule = x.Schedule.Rule,
                AllowedDays = x.Schedule.AllowedDays
            }
        });
    }
    private static Dto.ImportBackupOutputDto ExecuteImport(Connection connection, bool cmdline, bool import_metadata, bool direct, string passphrase, string tempfile)
    {
        try
        {
            if (cmdline)
                throw new BadRequestException("Import from commandline not yet implemented");

            var ipx = BackupImportExportHandler.LoadConfiguration(tempfile, import_metadata, () => passphrase);
            if (direct)
            {
                lock (connection.m_lock)
                {
                    var basename = ipx.Backup.Name;
                    var c = 0;
                    while (c++ < 100 && connection.Backups.Any(x => x.Name.Equals(ipx.Backup.Name, StringComparison.OrdinalIgnoreCase)))
                        ipx.Backup.Name = basename + " (" + c.ToString() + ")";

                    if (connection.Backups.Any(x => x.Name.Equals(ipx.Backup.Name, StringComparison.OrdinalIgnoreCase)))
                        throw new BadRequestException("There already exists a backup with that name");

                    var err = connection.ValidateBackup(ipx.Backup, ipx.Schedule);
                    if (!string.IsNullOrWhiteSpace(err))
                        throw new BadRequestException(err);

                    connection.AddOrUpdateBackupAndSchedule(ipx.Backup, ipx.Schedule);
                }

                return new Dto.ImportBackupOutputDto(ipx.Backup.ID, null);
            }
            else
            {
                return new Dto.ImportBackupOutputDto(null, ipx);
            }
        }
        catch (Exception ex)
        {
            connection.LogError("", "Failed to import backup", ex);
            throw new ServerErrorException($"Failed to import backup: {ex.Message}");
        }
    }

    private class WrappedBackup : Server.Database.Backup
    {
        public string? DBPathSetter
        {
            get => DBPath;
            set => SetDBPath(value);
        }
    }

    private static Dto.CreateBackupDto ExecuteAdd(Connection connection, Dto.BackupAndScheduleInputDto data, bool temporary, bool existingDb)
    {
        try
        {
            if (data.Backup == null)
                throw new BadRequestException("Data object had no backup entry");

            var filters = data.Backup.Filters ?? Array.Empty<Dto.BackupAndScheduleInputDto.FilterInputDto>();
            var settings = data.Backup.Settings ?? Array.Empty<Dto.BackupAndScheduleInputDto.SettingInputDto>();

            var backup = new WrappedBackup()
            {
                ID = null,
                Name = data.Backup.Name,
                Description = data.Backup.Description,
                Tags = data.Backup.Tags,
                TargetURL = data.Backup.TargetURL,
                DBPathSetter = null,
                Sources = data.Backup.Sources,
                Settings = settings.Select(x => new Setting()
                {
                    Name = x.Name,
                    Value = x.Value,
                    Filter = x.Filter
                }).ToArray(),
                Filters = filters.Select(x => new Filter()
                {
                    Order = x.Order,
                    Include = x.Include,
                    Expression = x.Expression
                }).ToArray(),
                Metadata = data.Backup.Metadata ?? new Dictionary<string, string>()
            };

            var schedule = data.Schedule == null ? null : new Schedule()
            {
                ID = data.Schedule.ID,
                Tags = data.Schedule.Tags,
                Time = data.Schedule.Time ?? new DateTime(0),
                Repeat = data.Schedule.Repeat,
                LastRun = data.Schedule.LastRun ?? new DateTime(0),
                Rule = data.Schedule.Rule,
                AllowedDays = data.Schedule.AllowedDays
            };

            if (temporary)
            {
                using (var tf = new Library.Utility.TempFile())
                    backup.DBPathSetter = tf;

                connection.RegisterTemporaryBackup(backup);
            }
            else
            {
                if (existingDb)
                {
                    backup.DBPathSetter = Library.Main.DatabaseLocator.GetDatabasePath(data.Backup.TargetURL, null, false, false);
                    if (string.IsNullOrWhiteSpace(data.Backup.DBPath))
                        throw new Exception("Unable to find remote db path?");
                }

                lock (connection.m_lock)
                {
                    if (connection.Backups.Any(x => x.Name.Equals(data.Backup.Name, StringComparison.OrdinalIgnoreCase)))
                        throw new ConflictException($"There already exists a backup with the name: {data.Backup.Name}");

                    var err = connection.ValidateBackup(backup, schedule);
                    if (!string.IsNullOrWhiteSpace(err))
                        throw new BadRequestException(err);

                    connection.AddOrUpdateBackupAndSchedule(backup, schedule);
                }
            }

            return new Dto.CreateBackupDto(backup.ID, backup.IsTemporary);
        }
        catch (Exception ex)
        {
            if (data == null)
                throw new BadRequestException($"Data object was null: {ex.Message}");

            if (ex is UserReportedHttpException)
                throw;

            throw new ServerErrorException($"Unable to save schedule or backup object: {ex.Message}");
        }
    }
}
