namespace Fast.Logic.FileService;

/// Base exception for all file service operations.
public class FileServiceException : Exception
{
    /// A unique correlation ID for tracing operations across the abstraction boundary.
    public string CorrelationId { get; }

    public string? FolderName { get; }
    public string? FileName { get; }

    /// The storage backend type that threw the exception.
    public FileServiceBackend Backend { get; }

    public FileServiceException(string message, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message)
    {
        CorrelationId = Guid.NewGuid().ToString("N")[..12];
        Backend = backend;
    }

    public FileServiceException(string message, string? folderName, string? fileName, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message)
    {
        CorrelationId = Guid.NewGuid().ToString("N")[..12];
        FolderName = folderName;
        FileName = fileName;
        Backend = backend;
    }

    public FileServiceException(string message, Exception innerException, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, innerException)
    {
        CorrelationId = Guid.NewGuid().ToString("N")[..12];
        Backend = backend;
    }

    public FileServiceException(string message, string? folderName, string? fileName, Exception innerException, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, innerException)
    {
        CorrelationId = Guid.NewGuid().ToString("N")[..12];
        FolderName = folderName;
        FileName = fileName;
        Backend = backend;
    }
    public override string ToString()
    {
        return $"[{CorrelationId}] {Backend}: {Message} (Folder: {FolderName ?? "N/A"}, File: {FileName ?? "N/A"}){Environment.NewLine}{base.ToString()}";
    }
}

public class FileServiceFileNotFoundException : FileServiceException
{
    public FileServiceFileNotFoundException(string folderName, string fileName, FileServiceBackend backend = FileServiceBackend.Unknown, string? details = null)
        : base($"File not found: {fileName} in folder {folderName}\n{details}", folderName, fileName, backend)
    {
    }

    public FileServiceFileNotFoundException(string folderName, string fileName, Exception innerException, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base($"File not found: {fileName} in folder {folderName}", folderName, fileName, innerException, backend)
    {
    }
}

public class FileServiceDirectoryNotFoundException : FileServiceException
{
    public FileServiceDirectoryNotFoundException(string folderName, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base($"Directory not found: {folderName}", folderName, null, backend)
    {
    }

    public FileServiceDirectoryNotFoundException(string folderName, Exception innerException, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base($"Directory not found: {folderName}", folderName, null, innerException, backend)
    {
    }
}

public class FileServiceAccessDeniedException : FileServiceException
{
    public FileServiceAccessDeniedException(string folderName, string? fileName, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base($"Access denied to {(fileName != null ? $"file {fileName} in " : "")}folder {folderName}", folderName, fileName, backend)
    {
    }

    public FileServiceAccessDeniedException(string folderName, string? fileName, Exception innerException, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base($"Access denied to {(fileName != null ? $"file {fileName} in " : "")}folder {folderName}", folderName, fileName, innerException, backend)
    {
    }
}

public class FileServiceFileExistsException : FileServiceException
{
    public FileServiceFileExistsException(string folderName, string fileName, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base($"File already exists: {fileName} in folder {folderName}", folderName, fileName, backend)
    {
    }
}

public class FileServiceThrottledException : FileServiceException
{
    /// Suggested retry delay from the service, if available.
    public TimeSpan? RetryAfter { get; }

    public FileServiceThrottledException(string message, TimeSpan? retryAfter = null, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, backend)
    {
        RetryAfter = retryAfter;
    }

    public FileServiceThrottledException(string message, Exception innerException, TimeSpan? retryAfter = null, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, innerException, backend)
    {
        RetryAfter = retryAfter;
    }
}

/// Exception thrown when the service circuit breaker is open due to repeated failures.
public class FileServiceCircuitOpenException : FileServiceException
{
    /// When the circuit breaker will attempt to close.
    public DateTime? RetryAt { get; }

    public FileServiceCircuitOpenException(string message, DateTime? retryAt = null, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, backend)
    {
        RetryAt = retryAt;
    }
}

/// Exception thrown when an upload fails (e.g., file too large, upload session expired).
public class FileServiceUploadException : FileServiceException
{
    /// The size of the file being uploaded, if known.
    public long? FileSize { get; }

    public FileServiceUploadException(string message, string folderName, string fileName, long? fileSize = null, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, folderName, fileName, backend)
    {
        FileSize = fileSize;
    }

    public FileServiceUploadException(string message, string folderName, string fileName, Exception innerException, long? fileSize = null, FileServiceBackend backend = FileServiceBackend.Unknown)
        : base(message, folderName, fileName, innerException, backend)
    {
        FileSize = fileSize;
    }
}

/// Identifies the storage backend type.
public enum FileServiceBackend
{
    Unknown = 0,
    Local = 1,
    SharePoint = 2
}