//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// 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 ContainerTestSupport
import Foundation
import Testing

/// Tests for `container system df`. All tests clear and inspect the global image
/// store, so they must run serially with no concurrent image activity.
@Suite(.serialized)
struct TestCLISystemDFSerial {
    private struct DiskUsageStats: Decodable {
        let images: ResourceUsage
    }
    private struct ResourceUsage: Decodable {
        let active: Int
        let reclaimable: UInt64
        let sizeInBytes: UInt64
        let total: Int
    }

    private let alpine = WarmupImage.alpine320.rawValue

    // Issue #1526: reported image size must include content blobs, not just unpacked snapshots.
    @Test func imageDiskUsageIsPopulatedAfterPull() async throws {
        try await ContainerFixture.with { f in
            try withCleanImageStore(f) {
                try f.restoreWarmupImage(.alpine320)
                let stats = try systemDiskUsage(f)
                #expect(stats.images.total >= 1)
                #expect(stats.images.active == 0)
                #expect(stats.images.sizeInBytes > 0)
                #expect(stats.images.reclaimable == stats.images.sizeInBytes)
            }
        }
    }

    // Issue #1527: tagging the same image must not double-count its storage.
    @Test func tagsDoNotDoubleCountImageStorage() async throws {
        try await ContainerFixture.with { f in
            try withCleanImageStore(f) {
                try f.restoreWarmupImage(.alpine320)
                let before = try systemDiskUsage(f)
                try f.doImageTag(self.alpine, newName: "local/system-df-alpine:tag-one")
                try f.doImageTag(self.alpine, newName: "local/system-df-alpine:tag-two")
                let after = try systemDiskUsage(f)
                #expect(after.images.total == before.images.total + 2)
                #expect(after.images.sizeInBytes == before.images.sizeInBytes)
                #expect(after.images.reclaimable == before.images.reclaimable)
            }
        }
    }

    // Issue #1527: removing one of several tags must not free shared storage.
    @Test func deletingOneOfMultipleTagsPreservesSharedStorage() async throws {
        try await ContainerFixture.with { f in
            try withCleanImageStore(f) {
                let baseline = try systemDiskUsage(f)
                try f.restoreWarmupImage(.alpine320)
                try f.doImageTag(self.alpine, newName: "local/system-df-alpine:delete-probe")
                let beforeDelete = try systemDiskUsage(f)

                try f.doRemoveImages(["local/system-df-alpine:delete-probe"])
                let afterAliasDelete = try systemDiskUsage(f)
                #expect(afterAliasDelete.images.total == beforeDelete.images.total - 1)
                #expect(afterAliasDelete.images.sizeInBytes == beforeDelete.images.sizeInBytes)
                #expect(afterAliasDelete.images.reclaimable == beforeDelete.images.reclaimable)

                _ = try? f.doRemoveImages()
                let afterFullClean = try systemDiskUsage(f)
                #expect(afterFullClean.images.total <= baseline.images.total)
                #expect(afterFullClean.images.sizeInBytes <= baseline.images.sizeInBytes)
            }
        }
    }

    // MARK: - Private helpers

    private func withCleanImageStore(_ f: ContainerFixture, _ body: () throws -> Void) throws {
        _ = try? f.doRemoveImages()
        defer { _ = try? f.doRemoveImages() }
        try body()
    }

    private func systemDiskUsage(_ f: ContainerFixture) throws -> DiskUsageStats {
        let result = try f.run(["system", "df", "--format", "json"]).check()
        return try JSONDecoder().decode(DiskUsageStats.self, from: result.outputData)
    }
}
