namespace Sbroenne.ExcelMcp.ComInterop;

/// <summary>
/// Utility class for validating file access and locking status.
/// Provides OS-level file lock detection and IRM/AIP-encryption detection before Excel COM operations.
/// </summary>
public static class FileAccessValidator
{
    // OLE2 Compound Document Format signature.
    // IRM/AIP-protected Excel files are stored as OLE2 containers with an EncryptedPackage
    // stream instead of the standard ZIP-based Office Open XML format.
    private static ReadOnlySpan<byte> Ole2Signature => [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];

    /// <summary>
    /// Detects if the file is IRM/AIP-protected by checking for the OLE2 compound document
    /// signature on files whose format should be ZIP-based (OOXML). Legacy .xls files are
    /// always OLE2 by design and are excluded from this check to avoid false positives.
    /// IRM-protected files must be opened as read-only with Excel visible so the user can
    /// authenticate through the Information Rights Management credential prompt.
    /// </summary>
    /// <param name="filePath">The file path to inspect.</param>
    /// <returns>
    /// <c>true</c> if the file has the OLE2 Compound Document header AND uses a modern
    /// OOXML extension (.xlsx, .xlsm, .xlsb), indicating IRM/AIP encryption;
    /// <c>false</c> for legacy .xls files (always OLE2), standard ZIP-based files, or
    /// if the file cannot be read.
    /// </returns>
    public static bool IsIrmProtected(string filePath)
    {
        if (!File.Exists(filePath))
            return false;

        // Legacy .xls/.xlt files are always OLE2 compound documents by design.
        // They are NOT IRM-protected just because they have an OLE2 header.
        var ext = Path.GetExtension(filePath);
        if (ext.Equals(".xls", StringComparison.OrdinalIgnoreCase) ||
            ext.Equals(".xlt", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        try
        {
            Span<byte> header = stackalloc byte[8];
            using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            int read = fs.Read(header);
            if (read < 8)
                return false;
            return header.SequenceEqual(Ole2Signature);
        }
        catch
        {
            // Cannot read → treat as not IRM so normal error handling takes over
            return false;
        }
    }

    /// <summary>
    /// Validates that a file is not locked by attempting to open it with exclusive access.
    /// Throws InvalidOperationException if file is locked or inaccessible.
    /// This is a fast OS-level check that doesn't require launching Excel.
    /// </summary>
    /// <param name="filePath">The file path to validate</param>
    /// <exception cref="InvalidOperationException">Thrown when file is locked or inaccessible</exception>
    public static void ValidateFileNotLocked(string filePath)
    {
        try
        {
            using var lockTest = new FileStream(
                filePath,
                FileMode.Open,
                FileAccess.ReadWrite,
                FileShare.None);
            // File is NOT locked - close and proceed
        }
        catch (IOException ioEx)
        {
            // File is locked by another process (most likely already open in Excel)
            throw CreateFileLockedError(filePath, ioEx);
        }
        catch (UnauthorizedAccessException uaEx)
        {
            // File access denied (permissions issue or file is locked)
            throw new InvalidOperationException(
                $"Cannot access '{Path.GetFileName(filePath)}'. " +
                "The file may be read-only, you may lack permissions, or it's locked by another process. " +
                "Please verify file permissions and close any applications using this file.",
                uaEx);
        }
    }

    /// <summary>
    /// Creates a standardized InvalidOperationException for file-locked scenarios.
    /// Provides consistent error messages across the codebase.
    /// </summary>
    /// <param name="filePath">The file path that is locked</param>
    /// <param name="innerException">The underlying exception that triggered the error</param>
    /// <returns>A user-friendly InvalidOperationException with guidance</returns>
    public static InvalidOperationException CreateFileLockedError(string filePath, Exception innerException)
    {
        return new InvalidOperationException(
            $"Cannot open '{Path.GetFileName(filePath)}'. " +
            "The file is already open in Excel or another process is using it. " +
            "Please close the file before running automation commands. " +
            "ExcelMcp requires exclusive access to workbooks during operations.",
            innerException);
    }
}


