//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

import ContainerizationOCI
import Foundation
import Testing

@testable import ContainerBuildIR

struct DigestAndPlatformTests {

    // MARK: - Digest Algorithm Tests

    @Test func digestSHA256Creation() throws {
        let data = "Hello, World!".data(using: .utf8)!
        let digest = try Digest.compute(data, using: .sha256)

        #expect(digest.algorithm == .sha256)
        #expect(digest.bytes.count == 32)  // SHA256 produces 32 bytes

        // Verify deterministic computation
        let digest2 = try Digest.compute(data, using: .sha256)
        #expect(digest == digest2)

        // Verify string format
        let stringValue = digest.stringValue
        #expect(stringValue.hasPrefix("sha256:"))
        #expect(stringValue.count == "sha256:".count + 64)  // 32 bytes = 64 hex chars
    }

    @Test func digestSHA384Creation() throws {
        let data = "Test data for SHA384".data(using: .utf8)!
        let digest = try Digest.compute(data, using: .sha384)

        #expect(digest.algorithm == .sha384)
        #expect(digest.bytes.count == 48)  // SHA384 produces 48 bytes

        // Verify string format
        let stringValue = digest.stringValue
        #expect(stringValue.hasPrefix("sha384:"))
        #expect(stringValue.count == "sha384:".count + 96)  // 48 bytes = 96 hex chars
    }

    @Test func digestSHA512Creation() throws {
        let data = "Test data for SHA512".data(using: .utf8)!
        let digest = try Digest.compute(data, using: .sha512)

        #expect(digest.algorithm == .sha512)
        #expect(digest.bytes.count == 64)  // SHA512 produces 64 bytes

        // Verify string format
        let stringValue = digest.stringValue
        #expect(stringValue.hasPrefix("sha512:"))
        #expect(stringValue.count == "sha512:".count + 128)  // 64 bytes = 128 hex chars
    }

    @Test func digestFromValidBytes() throws {
        // Create SHA256 digest from valid bytes
        let validBytes = Data(repeating: 0xAB, count: 32)
        let digest = try Digest(algorithm: .sha256, bytes: validBytes)

        #expect(digest.algorithm == .sha256)
        #expect(digest.bytes == validBytes)

        let expectedString = "sha256:" + String(repeating: "ab", count: 32)
        #expect(digest.stringValue == expectedString)
    }

    @Test func digestFromInvalidBytes() throws {
        // Test wrong length for SHA256
        let wrongLengthBytes = Data(repeating: 0xFF, count: 16)  // Should be 32
        #expect(throws: DigestError.self) {
            try Digest(algorithm: .sha256, bytes: wrongLengthBytes)
        }

        // Test wrong length for SHA384
        let wrongLengthBytes384 = Data(repeating: 0xFF, count: 32)  // Should be 48
        #expect(throws: DigestError.self) {
            try Digest(algorithm: .sha384, bytes: wrongLengthBytes384)
        }

        // Test wrong length for SHA512
        let wrongLengthBytes512 = Data(repeating: 0xFF, count: 32)  // Should be 64
        #expect(throws: DigestError.self) {
            try Digest(algorithm: .sha512, bytes: wrongLengthBytes512)
        }
    }

    // MARK: - Digest Parsing Tests

    @Test func digestParsingValidFormats() throws {
        let testCases = [
            ("sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", Digest.Algorithm.sha256),
            ("sha384:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", Digest.Algorithm.sha384),
            ("sha512:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", Digest.Algorithm.sha512),
        ]

        for (digestString, expectedAlgorithm) in testCases {
            let digest = try Digest(parsing: digestString)
            #expect(digest.algorithm == expectedAlgorithm)
            #expect(digest.stringValue == digestString)
        }
    }

    @Test func digestParsingInvalidFormats() throws {
        let invalidFormats = [
            "no-colon-separator",
            "sha256",
            "sha256:",
            "sha256:invalid-hex",
            "sha256:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",  // Invalid hex chars
            "unknown:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
            "sha256:short",  // Too short
            "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789extra",  // Too long
        ]

        for invalidFormat in invalidFormats {
            #expect(throws: DigestError.self) {
                try Digest(parsing: invalidFormat)
            }
        }
    }

    @Test func digestParsingMixedCase() throws {
        // Test that mixed case hex is handled correctly
        let upperCaseDigest = "sha256:ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789"
        let lowerCaseDigest = "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
        let mixedCaseDigest = "sha256:AbCdEf0123456789aBcDeF0123456789AbCdEf0123456789aBcDeF0123456789"

        let upperDigest = try Digest(parsing: upperCaseDigest)
        let lowerDigest = try Digest(parsing: lowerCaseDigest)
        let mixedDigest = try Digest(parsing: mixedCaseDigest)

        // All should parse to the same bytes
        #expect(upperDigest.bytes == lowerDigest.bytes)
        #expect(lowerDigest.bytes == mixedDigest.bytes)

        // String output should be lowercase
        #expect(upperDigest.stringValue == lowerCaseDigest)
        #expect(mixedDigest.stringValue == lowerCaseDigest)
    }

    // MARK: - Digest Content Hashing Tests

    @Test func digestContentHashing() throws {
        // Test that different content produces different digests
        let content1 = "First piece of content".data(using: .utf8)!
        let content2 = "Second piece of content".data(using: .utf8)!

        let digest1 = try Digest.compute(content1)
        let digest2 = try Digest.compute(content2)

        #expect(digest1 != digest2)
        #expect(digest1.stringValue != digest2.stringValue)

        // Test that same content produces same digest
        let digest1Copy = try Digest.compute(content1)
        #expect(digest1 == digest1Copy)
    }

    @Test func digestEmptyContent() throws {
        let emptyData = Data()
        let digest = try Digest.compute(emptyData)

        #expect(digest.algorithm == .sha256)  // Default algorithm
        #expect(digest.bytes.count == 32)

        // Known SHA256 of empty string
        let expectedEmptyDigest = try Digest(parsing: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
        #expect(digest == expectedEmptyDigest)
    }

    @Test func digestLargeContent() throws {
        // Test with larger content (1MB)
        let largeContent = Data(repeating: 0x42, count: 1024 * 1024)
        let digest = try Digest.compute(largeContent)

        #expect(digest.algorithm == .sha256)
        #expect(digest.bytes.count == 32)

        // Verify deterministic
        let digest2 = try Digest.compute(largeContent)
        #expect(digest == digest2)
    }

    // MARK: - Digest Codable Tests

    @Test func digestCodable() throws {
        let originalDigest = try Digest(parsing: "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789")

        // Encode to JSON
        let encoder = JSONEncoder()
        let data = try encoder.encode(originalDigest)

        // Decode from JSON
        let decoder = JSONDecoder()
        let decodedDigest = try decoder.decode(Digest.self, from: data)

        #expect(decodedDigest == originalDigest)
        #expect(decodedDigest.stringValue == originalDigest.stringValue)
    }

    // MARK: - Integration Tests

    @Test func digestAndPlatformIntegration() throws {
        // Test that digests and platforms work together in realistic scenarios
        let platform = Platform.linuxAMD64
        let operationData = "RUN apt-get update && apt-get install -y curl".data(using: .utf8)!
        let operationDigest = try Digest.compute(operationData)

        // Simulate cache key generation (simplified)
        let cacheKeyData = "\(operationDigest.stringValue):\(platform.description)".data(using: .utf8)!
        let cacheDigest = try Digest.compute(cacheKeyData)

        #expect(cacheDigest.algorithm == .sha256)
        #expect(cacheDigest.bytes.count == 32)

        // Different platform should produce different cache key
        let differentPlatform = Platform.linuxARM64
        let differentCacheKeyData = "\(operationDigest.stringValue):\(differentPlatform.description)".data(using: .utf8)!
        let differentCacheDigest = try Digest.compute(differentCacheKeyData)

        #expect(cacheDigest != differentCacheDigest)
    }

    @Test func multiAlgorithmDigestComparison() throws {
        let testData = "Container build test data".data(using: .utf8)!

        let sha256Digest = try Digest.compute(testData, using: .sha256)
        let sha384Digest = try Digest.compute(testData, using: .sha384)
        let sha512Digest = try Digest.compute(testData, using: .sha512)

        // All should be different (different algorithms)
        #expect(sha256Digest != sha384Digest)
        #expect(sha384Digest != sha512Digest)
        #expect(sha256Digest != sha512Digest)

        // Verify byte lengths
        #expect(sha256Digest.bytes.count == 32)
        #expect(sha384Digest.bytes.count == 48)
        #expect(sha512Digest.bytes.count == 64)

        // Verify string prefixes
        #expect(sha256Digest.stringValue.hasPrefix("sha256:"))
        #expect(sha384Digest.stringValue.hasPrefix("sha384:"))
        #expect(sha512Digest.stringValue.hasPrefix("sha512:"))
    }

    // MARK: - Error Message Tests

    @Test func digestErrorMessages() throws {
        do {
            _ = try Digest(algorithm: .sha256, bytes: Data(count: 16))
            Issue.record("Should have thrown an error")
        } catch let error as DigestError {
            switch error {
            case .invalidLength(let expected, let actual):
                #expect(expected == 32)
                #expect(actual == 16)
                #expect(error.errorDescription?.contains("expected 32") == true)
                #expect(error.errorDescription?.contains("got 16") == true)
            default:
                Issue.record("Wrong error type: \(error)")
            }
        }

        do {
            _ = try Digest(parsing: "invalid:format")
            Issue.record("Should have thrown an error")
        } catch let error as DigestError {
            switch error {
            case .unsupportedAlgorithm(let algo):
                #expect(algo == "invalid")
                #expect(error.errorDescription?.contains("Unsupported digest algorithm") == true)
            default:
                Issue.record("Wrong error type: \(error)")
            }
        }

        do {
            _ = try Digest(parsing: "sha256:invalid-hex")
            Issue.record("Should have thrown an error")
        } catch let error as DigestError {
            switch error {
            case .invalidHex(let hex):
                #expect(hex == "invalid-hex")
                #expect(error.errorDescription?.contains("Invalid hex") == true)
            default:
                Issue.record("Wrong error type: \(error)")
            }
        }
    }

    // MARK: - Performance Tests

    @Test func digestPerformance() throws {
        let testSizes = [1024, 10240, 102400]  // 1KB, 10KB, 100KB

        for size in testSizes {
            let data = Data(repeating: 0x42, count: size)

            let startTime = Date()
            let _ = try Digest.compute(data)
            let duration = Date().timeIntervalSince(startTime)

            print("Digest computation for \(size) bytes: \(String(format: "%.3f", duration))s")
            #expect(duration < 0.1, "Digest computation should be fast for \(size) bytes")
        }
    }
}
