//===----------------------------------------------------------------------===//
// 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 Foundation
import Testing

extension TestCLIBuildBase {
    class CLIBuilderLocalOutputTest: TestCLIBuildBase {
        override init() throws {
            try super.init()
        }

        deinit {
            try? builderDelete(force: true)
        }

        @Test func testBuildLocalOutputHappyPath() throws {
            let tempDir: URL = try createTempDir()

            // Test comprehensive multi-stage build with context and build arguments
            let dockerfile: String =
                """
                ARG MESSAGE=default
                FROM scratch AS builder
                ADD build.txt /build.txt
                ADD testfile.txt /hello.txt

                FROM scratch
                COPY --from=builder /build.txt /final.txt
                COPY --from=builder /hello.txt /app/hello.txt
                ADD message.txt /message.txt
                """
            let context: [FileSystemEntry] = [
                .file("build.txt", content: .data("Building stage\n".data(using: .utf8)!)),
                .file("testfile.txt", content: .data("Hello from local build\n".data(using: .utf8)!)),
                .file("message.txt", content: .data("Hello from build args\n".data(using: .utf8)!)),
            ]
            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)

            let outputDir = tempDir.appendingPathComponent("comprehensive-local-output")
            let imageName = "local-comprehensive-test:\(UUID().uuidString)"

            let response = try buildWithLocalOutput(
                tag: imageName,
                tempDir: tempDir,
                outputDir: outputDir,
                args: ["MESSAGE=Hello from build args"]
            )

            // Verify the build succeeded
            #expect(response.contains("Successfully exported to"), "Expected successful local export message")

            // Verify the output directory was created
            #expect(FileManager.default.fileExists(atPath: outputDir.path), "Expected local output directory to exist")

            // Verify the output contains expected structure
            let contents = try FileManager.default.contentsOfDirectory(atPath: outputDir.path)
            #expect(!contents.isEmpty, "Expected local output directory to contain files")

            // Test basic functionality - verify basic local output works
            let basicTempDir: URL = try createTempDir()
            let basicDockerfile: String =
                """
                FROM scratch

                ADD testfile.txt /hello.txt
                """
            let basicContext: [FileSystemEntry] = [
                .file("testfile.txt", content: .data("Hello from basic build\n".data(using: .utf8)!))
            ]
            try createContext(tempDir: basicTempDir, dockerfile: basicDockerfile, context: basicContext)

            let basicOutputDir = basicTempDir.appendingPathComponent("basic-local-output")
            let basicImageName = "local-basic-test:\(UUID().uuidString)"

            let basicResponse = try buildWithLocalOutput(tag: basicImageName, tempDir: basicTempDir, outputDir: basicOutputDir)

            // Verify basic build succeeded
            #expect(basicResponse.contains("Successfully exported to"), "Expected successful basic local export message")
            #expect(FileManager.default.fileExists(atPath: basicOutputDir.path), "Expected basic local output directory to exist")

            // Test context functionality - verify COPY works with context
            let contextTempDir: URL = try createTempDir()
            let contextDockerfile: String =
                """
                FROM scratch

                COPY testfile.txt /app/testfile.txt
                """
            let contextContext: [FileSystemEntry] = [
                .file("testfile.txt", content: .data("Test content for context build\n".data(using: .utf8)!))
            ]
            try createContext(tempDir: contextTempDir, dockerfile: contextDockerfile, context: contextContext)

            let contextOutputDir = contextTempDir.appendingPathComponent("context-local-output")
            let contextImageName = "local-context-test:\(UUID().uuidString)"

            let contextResponse = try buildWithLocalOutput(tag: contextImageName, tempDir: contextTempDir, outputDir: contextOutputDir)

            // Verify context build succeeded
            #expect(contextResponse.contains("Successfully exported to"), "Expected successful context local export message")
            #expect(FileManager.default.fileExists(atPath: contextOutputDir.path), "Expected context local output directory to exist")
        }

        @Test func testBuildLocalOutputEdgeCases() throws {
            // Test building with different context paths
            let dockerfileCtxDir: URL = try createTempDir()
            let dockerfile: String =
                """
                FROM scratch

                COPY . /app
                """
            let dockerfileCtx: [FileSystemEntry] = [
                .file("dockerfile-context.txt", content: .data("Dockerfile context file\n".data(using: .utf8)!))
            ]
            try createContext(tempDir: dockerfileCtxDir, dockerfile: dockerfile, context: dockerfileCtx)

            let buildContextDir: URL = try createTempDir()
            let buildContext: [FileSystemEntry] = [
                .file("build-context.txt", content: .data("Build context file\n".data(using: .utf8)!))
            ]
            try createContext(tempDir: buildContextDir, dockerfile: "", context: buildContext)

            let outputDir = dockerfileCtxDir.appendingPathComponent("diffpaths-local-output")
            let imageName = "local-diffpaths-test:\(UUID().uuidString)"

            let response = try buildWithPathsAndLocalOutput(
                tag: imageName,
                tempContext: buildContextDir,
                tempDockerfileContext: dockerfileCtxDir,
                outputDir: outputDir
            )

            // Verify the build succeeded
            #expect(response.contains("Successfully exported to"), "Expected successful local export message")

            // Verify the output directory exists
            #expect(FileManager.default.fileExists(atPath: outputDir.path), "Expected local output directory to exist")

            // Test building to existing output directory
            let existingTempDir: URL = try createTempDir()
            let existingDockerfile: String =
                """
                FROM scratch

                ADD newfile.txt /newfile.txt
                """
            let existingContext: [FileSystemEntry] = [
                .file("newfile.txt", content: .data("New content from build\n".data(using: .utf8)!))
            ]
            try createContext(tempDir: existingTempDir, dockerfile: existingDockerfile, context: existingContext)

            let existingOutputDir = existingTempDir.appendingPathComponent("existing-output")

            // Create the output directory and add some existing files
            try FileManager.default.createDirectory(at: existingOutputDir, withIntermediateDirectories: true)
            let existingFile = existingOutputDir.appendingPathComponent("existing.txt")
            try "Existing file content\n".data(using: .utf8)!.write(to: existingFile)

            let existingImageName = "local-existing-test:\(UUID().uuidString)"

            let existingResponse = try buildWithLocalOutput(tag: existingImageName, tempDir: existingTempDir, outputDir: existingOutputDir)

            // Verify the build succeeded
            #expect(existingResponse.contains("Successfully exported to"), "Expected successful local export message")

            // Verify the output directory exists
            #expect(FileManager.default.fileExists(atPath: existingOutputDir.path), "Expected local output directory to exist")

            // Verify the existing file is still there (local output should merge/overwrite)
            let contents = try FileManager.default.contentsOfDirectory(atPath: existingOutputDir.path)
            #expect(!contents.isEmpty, "Expected local output directory to contain files")

            // The behavior may vary - local output might overwrite the directory or merge contents
            // This test verifies that the operation completes successfully with an existing directory
        }

        @Test func testBuildLocalOutputFailure() throws {
            let tempDir: URL = try createTempDir()
            let dockerfile: String =
                """
                FROM scratch

                ADD test.txt /test.txt
                """
            let context: [FileSystemEntry] = [
                .file("test.txt", content: .data("test\n".data(using: .utf8)!))
            ]
            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)

            // Use a path that doesn't exist and can't be created (invalid parent)
            let invalidOutputDir = URL(fileURLWithPath: "/nonexistent/invalid/path")
            let imageName = "local-invalid-test:\(UUID().uuidString)"

            #expect(throws: CLIError.self) {
                try buildWithLocalOutput(tag: imageName, tempDir: tempDir, outputDir: invalidOutputDir)
            }
        }

        // Helper function to build with local output
        @discardableResult
        func buildWithLocalOutput(tag: String, tempDir: URL, outputDir: URL, args: [String]? = nil) throws -> String {
            try buildWithPathsAndLocalOutput(
                tag: tag,
                tempContext: tempDir,
                tempDockerfileContext: tempDir,
                outputDir: outputDir,
                args: args
            )
        }

        // Helper function to build with different paths and local output
        @discardableResult
        func buildWithPathsAndLocalOutput(
            tag: String,
            tempContext: URL,
            tempDockerfileContext: URL,
            outputDir: URL,
            args: [String]? = nil
        ) throws -> String {
            let contextDir: URL = tempContext.appendingPathComponent("context")
            let contextDirPath = contextDir.absoluteURL.path
            var buildArgs = [
                "build",
                "-f",
                tempDockerfileContext.appendingPathComponent("Dockerfile").path,
                "-t",
                tag,
                "--output",
                "type=local,dest=\(outputDir.path)",
            ]
            if let args = args {
                for arg in args {
                    buildArgs.append("--build-arg")
                    buildArgs.append(arg)
                }
            }
            buildArgs.append(contextDirPath)

            let response = try run(arguments: buildArgs)
            if response.status != 0 {
                throw CLIError.executionFailed("build failed: stdout=\(response.output) stderr=\(response.error)")
            }

            return response.output
        }
    }
}
