using Duplicati.Library.RestAPI.Abstractions;
using Duplicati.Server;
using Duplicati.Server.Database;
using Duplicati.Server.Serialization;
using Duplicati.Server.Serialization.Interface;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Exceptions;
using Microsoft.AspNetCore.Mvc;

namespace Duplicati.WebserverCore.Endpoints.V1.Backup;

public class BackupPost : IEndpointV1
{
    public static void Map(RouteGroupBuilder group)
    {
        group.MapPost("/backup/{id}/deletedb", ([FromServices] Connection connection, [FromRoute] string id)
            => ExecuteDeleteDb(GetBackup(connection, id)))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/movedb", ([FromServices] Connection connection, [FromRoute] string id, [FromBody] Dto.UpdateDbPathInputDto input)
            => UpdateDatabasePath(connection, GetBackup(connection, id), input.path, true))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/updatedb", ([FromServices] Connection connection, [FromRoute] string id, [FromBody] Dto.UpdateDbPathInputDto input)
            => UpdateDatabasePath(connection, GetBackup(connection, id), input.path, false))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/restore", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id, [FromBody] Dto.RestoreInputDto input)
            => ExecuteRestore(GetBackup(connection, id), workerThreadsManager, input))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/createreport", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteCreateReport(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/repair", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id, Dto.RepairInputDto? input)
            => ExecuteRepair(GetBackup(connection, id), workerThreadsManager, input))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/repairupdate", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id, Dto.RepairInputDto? input)
            => ExecuteRepairUpdate(GetBackup(connection, id), workerThreadsManager, input))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/vacuum", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteVacuum(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/verify", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteVerify(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/compact", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteCompact(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/start", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteRunBackup(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/run", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteRunBackup(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/report-remote-size", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromRoute] string id)
            => ExecuteReportRemoteSize(GetBackup(connection, id), workerThreadsManager))
            .RequireAuthorization();

        group.MapPost("/backup/{id}/copytotemp", ([FromServices] Connection connection, [FromRoute] string id)
            => ExecuteCopyToTemp(GetBackup(connection, id), connection))
            .RequireAuthorization();
    }

    private static IBackup GetBackup(Connection connection, string id)
        => connection.GetBackup(id) ?? throw new NotFoundException("Backup not found");

    private static void ExecuteDeleteDb(IBackup backup)
        => File.Delete(backup.DBPath);

    private static void UpdateDatabasePath(Connection connection, IBackup backup, string targetpath, bool move)
    {
        if (string.IsNullOrWhiteSpace(targetpath))
            throw new BadRequestException("No target path supplied");
        if (!Path.IsPathRooted(targetpath))
            throw new BadRequestException("Target path is relative, please supply a fully qualified path");

        if (move && (File.Exists(targetpath) || Directory.Exists(targetpath)))
            throw new ConflictException("A file already exists at the new location");

        if (move)
            File.Move(backup.DBPath, targetpath);

        connection.UpdateBackupDBPath(backup, targetpath);
    }



    private static Dto.TaskStartedDto ExecuteRestore(IBackup backup, IWorkerThreadsManager workerThreadsManager, Dto.RestoreInputDto input)
        => new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateRestoreTask(
            backup,
            input.paths ?? [],
            Library.Utility.Timeparser.ParseTimeInterval(input.time, DateTime.Now),
            input.restore_path,
            input.overwrite ?? false,
            input.permissions ?? false,
            input.skip_metadata ?? false,
            string.IsNullOrWhiteSpace(input.passphrase) ? null : input.passphrase)));

    private static Dto.TaskStartedDto ExecuteCreateReport(IBackup backup, IWorkerThreadsManager workerThreadsManager)
        => new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(DuplicatiOperation.CreateReport, backup)));

    private static Dto.TaskStartedDto ExecuteReportRemoteSize(IBackup backup, IWorkerThreadsManager workerThreadsManager)
        => new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(DuplicatiOperation.ListRemote, backup)));

    private static Dto.TaskStartedDto ExecuteRepair(IBackup backup, IWorkerThreadsManager workerThreadsManager, Dto.RepairInputDto? input)
        => DoRepair(backup, false, workerThreadsManager, input);

    private static Dto.TaskStartedDto ExecuteRepairUpdate(IBackup backup, IWorkerThreadsManager workerThreadsManager, Dto.RepairInputDto? input)
        => DoRepair(backup, true, workerThreadsManager, input);

    private static Dto.TaskStartedDto ExecuteVacuum(IBackup backup, IWorkerThreadsManager workerThreadsManager)
        => new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(DuplicatiOperation.Vacuum, backup)));

    private static Dto.TaskStartedDto ExecuteVerify(IBackup backup, IWorkerThreadsManager workerThreadsManager)
        => new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(DuplicatiOperation.Verify, backup)));

    private static Dto.TaskStartedDto ExecuteCompact(IBackup backup, IWorkerThreadsManager workerThreadsManager)
        => new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(DuplicatiOperation.Compact, backup)));

    private static Dto.TaskStartedDto DoRepair(IBackup backup, bool repairUpdate, IWorkerThreadsManager workerThreadsManager, Dto.RepairInputDto? input)
    {
        // These are all props on the input object
        var extra = new Dictionary<string, string>();
        if (input != null)
        {
            if (input.only_paths.HasValue)
                extra["repair-only-paths"] = input.only_paths.Value.ToString();
            if (!string.IsNullOrWhiteSpace(input.time))
                extra["time"] = input.time;
            if (!string.IsNullOrWhiteSpace(input.version))
                extra["version"] = input.version;
        }

        var filters = input?.paths ?? [];

        return new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(repairUpdate ? DuplicatiOperation.RepairUpdate : DuplicatiOperation.Repair, backup, extra, filters)));
    }

    private static Dto.TaskStartedDto ExecuteRunBackup(IBackup backup, IWorkerThreadsManager workerThreadsManager)
    {
        var t = workerThreadsManager.WorkerThread?.CurrentTask;
        var bt = t?.Backup;

        // Already running
        if (bt != null && backup.ID == bt.ID)
            return new Dto.TaskStartedDto("OK", t!.TaskID);

        t = workerThreadsManager.WorkerThread?.CurrentTasks.FirstOrDefault(x => x?.Backup != null && x.Backup.ID == backup.ID);
        if (t != null)
            return new Dto.TaskStartedDto("OK", t.TaskID);

        return new Dto.TaskStartedDto("OK", workerThreadsManager.AddTask(Runner.CreateTask(DuplicatiOperation.Backup, backup), true));
    }

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

    private static Dto.CreateBackupDto ExecuteCopyToTemp(IBackup backup, Connection connection)
    {
        var ipx = Serializer.Deserialize<WrappedBackup>(new StringReader(Newtonsoft.Json.JsonConvert.SerializeObject(backup)));

        using (var tf = new Library.Utility.TempFile())
            ipx.DBPathSetter = tf;
        ipx.ID = null;

        connection.RegisterTemporaryBackup(ipx);
        return new Dto.CreateBackupDto(ipx.ID, ipx.IsTemporary);
    }
}
