using System.Text.Json;
using PlotLine.Data;
using PlotLine.Models;

namespace PlotLine.Services;

public interface IProjectRestoreService
{
    Task<ProjectRestoreResult> RestoreAsync(int projectId, int projectRestorePointId);
}

public sealed class ProjectRestoreService(
    IProjectRestorePointRepository restorePointRepository,
    IProjectRestorePointService restorePointService,
    ILogger<ProjectRestoreService> logger) : IProjectRestoreService
{
    private static readonly JsonSerializerOptions ReadOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
        AllowTrailingCommas = true
    };

    private static readonly JsonSerializerOptions RestoreSqlOptions = new()
    {
        WriteIndented = false
    };

    public async Task<ProjectRestoreResult> RestoreAsync(int projectId, int projectRestorePointId)
    {
        var restorePoint = await restorePointRepository.GetAsync(projectRestorePointId, projectId);
        if (restorePoint?.JsonData is null)
        {
            return Failure("Restore point could not be found.");
        }

        ProjectBackupExport? backup;
        try
        {
            backup = JsonSerializer.Deserialize<ProjectBackupExport>(restorePoint.JsonData, ReadOptions);
        }
        catch (JsonException ex)
        {
            logger.LogWarning(ex, "Restore point {ProjectRestorePointID} contains invalid JSON.", projectRestorePointId);
            return Failure("Restore point JSON is invalid. No project data was changed.");
        }

        var validationMessage = ValidateBackup(projectId, backup);
        if (validationMessage is not null)
        {
            return Failure(validationMessage);
        }

        var safetyBackup = await restorePointService.CreatePreRestoreAsync(projectId, restorePoint.RestorePointName);
        if (safetyBackup is null)
        {
            logger.LogError("PreRestore backup creation failed for project {ProjectID}. Restore was aborted.", projectId);
            return Failure("A safety backup could not be created. Restore was aborted and no project data was changed.");
        }

        try
        {
            var restoreJson = JsonSerializer.Serialize(backup, RestoreSqlOptions);
            await restorePointRepository.RestoreAsync(projectId, projectRestorePointId, safetyBackup.ProjectRestorePointID, restoreJson);
            logger.LogInformation("Project {ProjectID} restored from restore point {ProjectRestorePointID}. Safety backup {SafetyRestorePointID} was created first.",
                projectId,
                projectRestorePointId,
                safetyBackup.ProjectRestorePointID);

            return new ProjectRestoreResult
            {
                Succeeded = true,
                Message = "Project restored. A safety backup of the previous state was created first.",
                SafetyBackup = safetyBackup
            };
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Project restore failed for project {ProjectID} from restore point {ProjectRestorePointID}. Safety backup {SafetyRestorePointID} was retained.",
                projectId,
                projectRestorePointId,
                safetyBackup.ProjectRestorePointID);

            return new ProjectRestoreResult
            {
                Succeeded = false,
                Message = "Restore failed. The safety backup was created, and the project data was left unchanged.",
                SafetyBackup = safetyBackup
            };
        }
    }

    private static string? ValidateBackup(int projectId, ProjectBackupExport? backup)
    {
        if (backup is null)
        {
            return "Restore point JSON is invalid. No project data was changed.";
        }

        if (!string.Equals(backup.Format, "PlotDirector.ProjectBackup", StringComparison.Ordinal))
        {
            return "Restore point format is not recognised. No project data was changed.";
        }

        if (!string.Equals(backup.FormatVersion, "1.0", StringComparison.Ordinal))
        {
            return "Restore point format version is not supported. No project data was changed.";
        }

        if (backup.Project?.ProjectID != projectId)
        {
            return "This restore point does not belong to the selected project. No project data was changed.";
        }

        return null;
    }

    private static ProjectRestoreResult Failure(string message) => new()
    {
        Succeeded = false,
        Message = message
    };
}
