import XCTest
@testable import Rosetta

/// Comprehensive tests for file attachment download, caching, preview parsing,
/// icon mapping, and cross-platform parity.
final class FileAttachmentTests: XCTestCase {

    // =========================================================================
    // MARK: - AttachmentPreviewCodec: File Preview Parsing
    // =========================================================================

    func testParseFilePreview_TagSizeFilename() {
        let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::12345::document.pdf"
        let parsed = AttachmentPreviewCodec.parseFilePreview(preview)
        XCTAssertEqual(parsed.downloadTag, "4e6712a0-31c0-4b0d-b17c-83170709ec02")
        XCTAssertEqual(parsed.fileSize, 12345)
        XCTAssertEqual(parsed.fileName, "document.pdf")
    }

    func testParseFilePreview_NoTag() {
        let preview = "12345::document.pdf"
        let parsed = AttachmentPreviewCodec.parseFilePreview(preview)
        XCTAssertEqual(parsed.downloadTag, "")
        XCTAssertEqual(parsed.fileSize, 12345)
        XCTAssertEqual(parsed.fileName, "document.pdf")
    }

    func testParseFilePreview_PlaceholderBeforeUpload() {
        let preview = "::500::notes.txt"
        let parsed = AttachmentPreviewCodec.parseFilePreview(preview)
        XCTAssertEqual(parsed.downloadTag, "")
        XCTAssertEqual(parsed.fileSize, 500)
        XCTAssertEqual(parsed.fileName, "notes.txt")
    }

    func testParseFilePreview_EmptyPreview() {
        let parsed = AttachmentPreviewCodec.parseFilePreview("")
        XCTAssertEqual(parsed.downloadTag, "")
        XCTAssertEqual(parsed.fileName, "file") // fallback
        XCTAssertEqual(parsed.fileSize, 0)
    }

    func testParseFilePreview_CustomFallbacks() {
        let parsed = AttachmentPreviewCodec.parseFilePreview(
            "", fallbackFileName: "unknown.bin", fallbackFileSize: 999
        )
        XCTAssertEqual(parsed.fileName, "unknown.bin")
        XCTAssertEqual(parsed.fileSize, 999)
    }

    func testParseFilePreview_FilenameWithColons() {
        // Edge case: filename containing "::" (unlikely but possible)
        let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::1024::file::with::colons.txt"
        let parsed = AttachmentPreviewCodec.parseFilePreview(preview)
        XCTAssertEqual(parsed.downloadTag, "4e6712a0-31c0-4b0d-b17c-83170709ec02")
        XCTAssertEqual(parsed.fileSize, 1024)
        XCTAssertEqual(parsed.fileName, "file::with::colons.txt")
    }

    // =========================================================================
    // MARK: - AttachmentPreviewCodec: Download Tag Extraction
    // =========================================================================

    func testDownloadTag_ValidUUID() {
        let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::blurhash"
        XCTAssertEqual(
            AttachmentPreviewCodec.downloadTag(from: preview),
            "4e6712a0-31c0-4b0d-b17c-83170709ec02"
        )
    }

    func testDownloadTag_NoTag() {
        let preview = "::blurhash"
        XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: preview), "")
    }

    func testDownloadTag_EmptyPreview() {
        XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: ""), "")
    }

    func testDownloadTag_JustBlurhash() {
        let preview = "LVRv{GtRSXWB"
        XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: preview), "")
    }

    // =========================================================================
    // MARK: - AttachmentPreviewCodec: BlurHash Extraction
    // =========================================================================

    func testBlurHash_TagAndHash() {
        let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::LVRv{GtR"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR")
    }

    func testBlurHash_WithDimensionSuffix() {
        let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::LVRv{GtR|640x480"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR")
    }

    func testBlurHash_PlainHash() {
        let preview = "LVRv{GtRSXWB"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtRSXWB")
    }

    func testBlurHash_PlaceholderFormat() {
        let preview = "::LVRv{GtR"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR")
    }

    func testBlurHash_LegacyNonUUIDTagPrefix() {
        let preview = "jbov1nac::LVRv{GtRSXWB"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtRSXWB")
    }

    func testBlurHash_LegacyNonUUIDTagWithDimensions() {
        let preview = "jbov1nac::LVRv{GtR|640x480"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR")
    }

    func testBlurHash_DoesNotStripUnknownNonUUIDPrefix() {
        let preview = "legacy_upload_id::LVRv{GtRSXWB"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "legacy_upload_id::LVRv{GtRSXWB")
    }

    func testBlurHash_LegacyNonUUIDTagWithEmptySuffix() {
        let preview = "jbov1nac::"
        XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "")
    }

    // =========================================================================
    // MARK: - AttachmentPreviewCodec: Image Dimensions
    // =========================================================================

    func testImageDimensions_Present() {
        let preview = "tag::LVRv{GtR|640x480"
        let dims = AttachmentPreviewCodec.imageDimensions(from: preview)
        XCTAssertEqual(dims?.width, 640)
        XCTAssertEqual(dims?.height, 480)
    }

    func testImageDimensions_Missing() {
        let preview = "tag::LVRv{GtR"
        XCTAssertNil(AttachmentPreviewCodec.imageDimensions(from: preview))
    }

    func testImageDimensions_TooSmall() {
        let preview = "tag::LVRv{GtR|5x5"
        XCTAssertNil(AttachmentPreviewCodec.imageDimensions(from: preview))
    }

    // =========================================================================
    // MARK: - AttachmentPreviewCodec: Call Duration
    // =========================================================================

    func testCallDuration_PlainNumber() {
        XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds("45"), 45)
    }

    func testCallDuration_Zero() {
        XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds("0"), 0)
    }

    func testCallDuration_Empty() {
        XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds(""), 0)
    }

    func testCallDuration_WithTag() {
        let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::60"
        XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds(preview), 60)
    }

    // =========================================================================
    // MARK: - AttachmentPreviewCodec: Compose
    // =========================================================================

    func testCompose_TagAndPayload() {
        let result = AttachmentPreviewCodec.compose(
            downloadTag: "4e6712a0-31c0-4b0d-b17c-83170709ec02",
            payload: "LVRv{GtR"
        )
        XCTAssertEqual(result, "4e6712a0-31c0-4b0d-b17c-83170709ec02::LVRv{GtR")
    }

    func testCompose_EmptyTag() {
        XCTAssertEqual(AttachmentPreviewCodec.compose(downloadTag: "", payload: "LVRv"), "LVRv")
    }

    // =========================================================================
    // MARK: - MessageAttachment: effectiveDownloadTag
    // =========================================================================

    func testEffectiveTag_TransportTagPresent() {
        let att = MessageAttachment(
            id: "test", preview: "old-tag::hash", transportTag: "new-tag"
        )
        XCTAssertEqual(att.effectiveDownloadTag, "new-tag")
    }

    func testEffectiveTag_FallbackToPreview() {
        let att = MessageAttachment(
            id: "test",
            preview: "4e6712a0-31c0-4b0d-b17c-83170709ec02::hash"
        )
        XCTAssertEqual(att.effectiveDownloadTag, "4e6712a0-31c0-4b0d-b17c-83170709ec02")
    }

    func testEffectiveTag_BothEmpty() {
        let att = MessageAttachment(id: "test", preview: "::hash")
        XCTAssertEqual(att.effectiveDownloadTag, "")
    }

    // =========================================================================
    // MARK: - File Icon Mapping (Telegram parity)
    // =========================================================================

    /// Tests the file icon mapping used by MessageFileView (SwiftUI) and
    /// NativeMessageCell.fileIcon(for:) (UIKit). Both must return same values.
    private func fileIcon(for fileName: String) -> String {
        let ext = (fileName as NSString).pathExtension.lowercased()
        switch ext {
        case "pdf": return "doc.fill"
        case "zip", "rar", "7z": return "doc.zipper"
        case "jpg", "jpeg", "png", "gif": return "photo.fill"
        case "mp4", "mov", "avi": return "film.fill"
        case "mp3", "wav", "aac": return "waveform"
        default: return "doc.fill"
        }
    }

    func testFileIcon_PDF() {
        XCTAssertEqual(fileIcon(for: "report.pdf"), "doc.fill")
    }

    func testFileIcon_ZIP() {
        XCTAssertEqual(fileIcon(for: "archive.zip"), "doc.zipper")
    }

    func testFileIcon_RAR() {
        XCTAssertEqual(fileIcon(for: "data.rar"), "doc.zipper")
    }

    func testFileIcon_7Z() {
        XCTAssertEqual(fileIcon(for: "backup.7z"), "doc.zipper")
    }

    func testFileIcon_JPG() {
        XCTAssertEqual(fileIcon(for: "photo.jpg"), "photo.fill")
    }

    func testFileIcon_JPEG() {
        XCTAssertEqual(fileIcon(for: "photo.jpeg"), "photo.fill")
    }

    func testFileIcon_PNG() {
        XCTAssertEqual(fileIcon(for: "screenshot.png"), "photo.fill")
    }

    func testFileIcon_GIF() {
        XCTAssertEqual(fileIcon(for: "animation.gif"), "photo.fill")
    }

    func testFileIcon_MP4() {
        XCTAssertEqual(fileIcon(for: "video.mp4"), "film.fill")
    }

    func testFileIcon_MOV() {
        XCTAssertEqual(fileIcon(for: "clip.mov"), "film.fill")
    }

    func testFileIcon_AVI() {
        XCTAssertEqual(fileIcon(for: "movie.avi"), "film.fill")
    }

    func testFileIcon_MP3() {
        XCTAssertEqual(fileIcon(for: "song.mp3"), "waveform")
    }

    func testFileIcon_WAV() {
        XCTAssertEqual(fileIcon(for: "audio.wav"), "waveform")
    }

    func testFileIcon_AAC() {
        XCTAssertEqual(fileIcon(for: "voice.aac"), "waveform")
    }

    func testFileIcon_Unknown() {
        XCTAssertEqual(fileIcon(for: "data.bin"), "doc.fill")
    }

    func testFileIcon_NoExtension() {
        XCTAssertEqual(fileIcon(for: "Makefile"), "doc.fill")
    }

    func testFileIcon_UpperCase() {
        XCTAssertEqual(fileIcon(for: "IMAGE.PNG"), "photo.fill")
    }

    // =========================================================================
    // MARK: - AttachmentCache: File Save/Load Round-Trip
    // =========================================================================

    func testFileSaveAndLoad_Plaintext() {
        // Without private key, files are saved as plaintext
        let cache = AttachmentCache.shared
        let originalKey = cache.privateKey
        cache.privateKey = nil
        defer { cache.privateKey = originalKey }

        let testData = "Hello, world!".data(using: .utf8)!
        let attId = "test_plain_\(UUID().uuidString.prefix(8))"
        let fileName = "test.txt"

        let url = cache.saveFile(testData, forAttachmentId: attId, fileName: fileName)
        XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))

        // fileURL should find it
        let foundURL = cache.fileURL(forAttachmentId: attId, fileName: fileName)
        XCTAssertNotNil(foundURL)

        // loadFileData should return original data
        let loaded = cache.loadFileData(forAttachmentId: attId, fileName: fileName)
        XCTAssertEqual(loaded, testData)

        // Cleanup
        try? FileManager.default.removeItem(at: url)
    }

    func testFileSaveAndLoad_Encrypted() throws {
        let cache = AttachmentCache.shared
        let originalKey = cache.privateKey

        // Use a test key
        cache.privateKey = "test_private_key_for_encryption"
        defer { cache.privateKey = originalKey }

        let testData = "Encrypted file content 🔒".data(using: .utf8)!
        let attId = "test_enc_\(UUID().uuidString.prefix(8))"
        let fileName = "secret.pdf"

        let url = cache.saveFile(testData, forAttachmentId: attId, fileName: fileName)
        XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))
        XCTAssertTrue(url.lastPathComponent.hasSuffix(".enc"))

        // Raw file should NOT be the original data (it's encrypted)
        let rawData = try Data(contentsOf: url)
        XCTAssertNotEqual(rawData, testData)

        // loadFileData should decrypt and return original
        let loaded = cache.loadFileData(forAttachmentId: attId, fileName: fileName)
        XCTAssertEqual(loaded, testData)

        // Cleanup
        try? FileManager.default.removeItem(at: url)
    }

    func testFileURL_NotCached() {
        let url = AttachmentCache.shared.fileURL(
            forAttachmentId: "nonexistent_\(UUID())", fileName: "nope.txt"
        )
        XCTAssertNil(url)
    }

    func testLoadFileData_NotCached() {
        let data = AttachmentCache.shared.loadFileData(
            forAttachmentId: "nonexistent_\(UUID())", fileName: "nope.txt"
        )
        XCTAssertNil(data)
    }

    func testFileSave_SlashInFilename() {
        let cache = AttachmentCache.shared
        let originalKey = cache.privateKey
        cache.privateKey = nil
        defer { cache.privateKey = originalKey }

        let testData = "data".data(using: .utf8)!
        let attId = "test_slash_\(UUID().uuidString.prefix(8))"
        // Slash should be replaced with underscore
        let fileName = "path/to/file.txt"

        let url = cache.saveFile(testData, forAttachmentId: attId, fileName: fileName)
        XCTAssertFalse(url.lastPathComponent.contains("/"))

        let loaded = cache.loadFileData(forAttachmentId: attId, fileName: fileName)
        XCTAssertEqual(loaded, testData)

        try? FileManager.default.removeItem(at: url)
    }

    // =========================================================================
    // MARK: - AttachmentType JSON Encode/Decode (cross-platform parity)
    // =========================================================================

    func testAttachmentType_EncodeValues() throws {
        // Verify wire format matches Desktop/Android (0,1,2,3,4)
        let encoder = JSONEncoder()
        let types: [AttachmentType] = [.image, .messages, .file, .avatar, .call]
        for (idx, type) in types.enumerated() {
            let data = try encoder.encode(type)
            let str = String(data: data, encoding: .utf8)!
            XCTAssertEqual(str, "\(idx)", "AttachmentType.\(type) should encode as \(idx)")
        }
    }

    func testMessageAttachment_BackwardCompatDecode() throws {
        // Old DB JSON without transportTag/transportServer should decode fine
        let json = #"{"id":"abc","preview":"tag::hash","blob":"data","type":0}"#
        let att = try JSONDecoder().decode(MessageAttachment.self, from: Data(json.utf8))
        XCTAssertEqual(att.id, "abc")
        XCTAssertEqual(att.type, .image)
        XCTAssertEqual(att.transportTag, "") // default
        XCTAssertEqual(att.transportServer, "") // default
    }

    func testMessageAttachment_NewFieldsDecode() throws {
        let json = #"{"id":"abc","preview":"hash","blob":"","type":3,"transportTag":"cdn-tag","transportServer":"https://cdn.example.com"}"#
        let att = try JSONDecoder().decode(MessageAttachment.self, from: Data(json.utf8))
        XCTAssertEqual(att.type, .avatar)
        XCTAssertEqual(att.transportTag, "cdn-tag")
        XCTAssertEqual(att.transportServer, "https://cdn.example.com")
    }

    // =========================================================================
    // MARK: - File Size Formatting
    // =========================================================================

    private func formattedFileSize(bytes: Int) -> String {
        if bytes < 1024 { return "\(bytes) B" }
        if bytes < 1024 * 1024 { return String(format: "%.1f KB", Double(bytes) / 1024) }
        if bytes < 1024 * 1024 * 1024 { return String(format: "%.1f MB", Double(bytes) / (1024 * 1024)) }
        return String(format: "%.1f GB", Double(bytes) / (1024 * 1024 * 1024))
    }

    func testFileSize_Bytes() {
        XCTAssertEqual(formattedFileSize(bytes: 500), "500 B")
    }

    func testFileSize_KB() {
        XCTAssertEqual(formattedFileSize(bytes: 2048), "2.0 KB")
    }

    func testFileSize_MB() {
        XCTAssertEqual(formattedFileSize(bytes: 5_242_880), "5.0 MB")
    }

    func testFileSize_GB() {
        XCTAssertEqual(formattedFileSize(bytes: 1_073_741_824), "1.0 GB")
    }

    func testFileSize_Zero() {
        XCTAssertEqual(formattedFileSize(bytes: 0), "0 B")
    }
}
