import PathKit
import ProjectSpec
import Spectre
import XcodeGenKit
import XcodeProj
import XCTest
import Yams
import TestSupport

private let app = Target(
    name: "MyApp",
    type: .application,
    platform: .iOS,
    dependencies: [Dependency(type: .target, reference: "MyFramework")]
)

private let framework = Target(
    name: "MyFramework",
    type: .framework,
    platform: .iOS
)

private let frameworkTest = Target(
    name: "MyFrameworkTests",
    type: .unitTestBundle,
    platform: .iOS
)

private let optionalFramework = Target(
    name: "MyOptionalFramework",
    type: .framework,
    platform: .iOS
)

private let uiTest = Target(
    name: "MyAppUITests",
    type: .uiTestBundle,
    platform: .iOS,
    dependencies: [Dependency(type: .target, reference: "MyApp")]
)

class SchemeGeneratorTests: XCTestCase {

    func testSchemes() throws {
        try skipIfNecessary()
        describe {

            let buildTarget = Scheme.BuildTarget(target: .local(app.name))
            $0.it("generates scheme") {
                let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name)
                let simulateLocation = Scheme.SimulateLocation(allow: true, defaultLocation: "New York, NY, USA")
                let storeKitConfiguration = "Configuration.storekit"
                let scheme = try Scheme(
                    name: "MyScheme",
                    build: Scheme.Build(targets: [buildTarget], preActions: [preAction]),
                    run: Scheme.Run(config: "Debug", enableGPUFrameCaptureMode: .metal, askForAppToLaunch: true, launchAutomaticallySubstyle: "2", simulateLocation: simulateLocation, storeKitConfiguration: storeKitConfiguration, customLLDBInit: "/sample/.lldbinit", customWorkingDirectory: "/test"),
                    test: Scheme.Test(config: "Debug", targets: [
                        Scheme.Test.TestTarget(targetReference: TestableTargetReference(framework.name), location: "test.gpx"),
                        Scheme.Test.TestTarget(targetReference: TestableTargetReference(framework.name), location: "New York, NY, USA")
                    ], customLLDBInit: "/test/.lldbinit"),
                    profile: Scheme.Profile(config: "Release", askForAppToLaunch: true)
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme],
                    options: .init(schemePathPrefix: "../")
                )
                let xcodeProject = try project.generateXcodeProject()
                let target = try unwrap(xcodeProject.pbxproj.nativeTargets
                    .first(where: { $0.name == app.name }))
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(scheme.name) == "MyScheme"
                try expect(xcscheme.buildAction?.buildImplicitDependencies) == true
                try expect(xcscheme.buildAction?.parallelizeBuild) == true
                try expect(xcscheme.buildAction?.runPostActionsOnFailure) == false
                try expect(xcscheme.buildAction?.preActions.first?.title) == "Script"
                try expect(xcscheme.buildAction?.preActions.first?.scriptText) == "echo Starting"
                try expect(xcscheme.buildAction?.preActions.first?.environmentBuildable?.buildableName) == "MyApp.app"
                try expect(xcscheme.buildAction?.preActions.first?.environmentBuildable?.blueprintName) == "MyApp"
                let buildActionEntry = try unwrap(xcscheme.buildAction?.buildActionEntries.first)
                try expect(buildActionEntry.buildFor) == BuildType.all

                let buildableReferences: [XCScheme.BuildableReference] = [
                    buildActionEntry.buildableReference,
                    xcscheme.launchAction?.runnable?.buildableReference,
                    xcscheme.profileAction?.buildableProductRunnable?.buildableReference,
                    xcscheme.testAction?.macroExpansion,
                ].compactMap { $0 }

                for buildableReference in buildableReferences {
                    // FIXME: try expect(buildableReference.blueprintIdentifier) == target.reference
                    try expect(buildableReference.blueprintName) == target.name
                    try expect(buildableReference.buildableName) == "\(target.name).\(target.productType!.fileExtension!)"
                }

                try expect(xcscheme.launchAction?.buildConfiguration) == "Debug"
                try expect(xcscheme.testAction?.buildConfiguration) == "Debug"
                try expect(xcscheme.profileAction?.buildConfiguration) == "Release"
                try expect(xcscheme.analyzeAction?.buildConfiguration) == "Debug"
                try expect(xcscheme.archiveAction?.buildConfiguration) == "Release"

                try expect(xcscheme.launchAction?.selectedDebuggerIdentifier) == XCScheme.defaultDebugger
                try expect(xcscheme.testAction?.selectedDebuggerIdentifier) == XCScheme.defaultDebugger

                try expect(xcscheme.launchAction?.askForAppToLaunch) == true
                try expect(xcscheme.profileAction?.askForAppToLaunch) == true
                try expect(xcscheme.launchAction?.launchAutomaticallySubstyle) == "2"
                try expect(xcscheme.launchAction?.allowLocationSimulation) == true
                try expect(xcscheme.launchAction?.storeKitConfigurationFileReference?.identifier) == "../Configuration.storekit"
                try expect(xcscheme.launchAction?.locationScenarioReference?.referenceType) == Scheme.SimulateLocation.ReferenceType.predefined.rawValue
                try expect(xcscheme.launchAction?.locationScenarioReference?.identifier) == "New York, NY, USA"
                try expect(xcscheme.launchAction?.customLLDBInitFile) == "/sample/.lldbinit"
                try expect(xcscheme.launchAction?.enableGPUFrameCaptureMode) == .metal
                try expect(xcscheme.testAction?.customLLDBInitFile) == "/test/.lldbinit"
                try expect(xcscheme.testAction?.systemAttachmentLifetime).to.beNil()

                try expect(xcscheme.launchAction?.useCustomWorkingDirectory) == true
                try expect(xcscheme.launchAction?.customWorkingDirectory) == "/test"

                try expect(xcscheme.testAction?.testables[0].locationScenarioReference?.referenceType) == "0"
                try expect(xcscheme.testAction?.testables[0].locationScenarioReference?.identifier) == "../test.gpx"
                
                try expect(xcscheme.testAction?.testables[1].locationScenarioReference?.referenceType) == "1"
                try expect(xcscheme.testAction?.testables[1].locationScenarioReference?.identifier) == "New York, NY, USA"
            }

            let frameworkTarget = Scheme.BuildTarget(target: .local(framework.name), buildTypes: [.archiving])
            $0.it("generates a scheme with the first runnable selected") {
                let scheme = Scheme(
                    name: "MyScheme",
                    build: Scheme.Build(targets: [frameworkTarget, buildTarget])
                )
                let project = Project(
                    name: "test",
                    targets: [framework, app],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                let buildableReference = xcscheme.launchAction?.runnable?.buildableReference
                try expect(buildableReference?.buildableName) == "MyApp.app"
            }

            $0.it("generates scheme with multiple configs") {
                let configs: [Config] = [
                    Config(name: "Beta", type: .debug),
                    Config(name: "Debug", type: .debug),
                    Config(name: "Production", type: .release),
                    Config(name: "Release", type: .release),
                ]
                let framework = Target(
                    name: "MyFramework",
                    type: .application,
                    platform: .iOS,
                    scheme: TargetScheme(testTargets: ["MyFrameworkTests"], storeKitConfiguration: "Configuration.storekit")
                )
                let project = Project(
                    name: "test",
                    configs: configs,
                    targets: [framework, frameworkTest],
                    options: .init(schemePathPrefix: "../../")
                )
                let xcodeProject = try project.generateXcodeProject()
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                try expect(xcscheme.launchAction?.buildConfiguration) == "Debug"
                try expect(xcscheme.launchAction?.storeKitConfigurationFileReference?.identifier) == "../../Configuration.storekit"
                try expect(xcscheme.testAction?.buildConfiguration) == "Debug"
                try expect(xcscheme.profileAction?.buildConfiguration) == "Release"
                try expect(xcscheme.analyzeAction?.buildConfiguration) == "Debug"
                try expect(xcscheme.archiveAction?.buildConfiguration) == "Release"
            }

            $0.it("sets environment variables for a scheme") {
                let runVariables: [XCScheme.EnvironmentVariable] = [
                    XCScheme.EnvironmentVariable(variable: "RUN_ENV", value: "ENABLED", enabled: true),
                    XCScheme.EnvironmentVariable(variable: "OTHER_RUN_ENV", value: "DISABLED", enabled: false),
                ]

                let scheme = Scheme(
                    name: "EnvironmentVariablesScheme",
                    build: Scheme.Build(targets: [buildTarget]),
                    run: Scheme.Run(config: "Debug", environmentVariables: runVariables, simulateLocation: .init(allow: true, defaultLocation: "File.gpx"), storeKitConfiguration: "Configuration.storekit"),
                    test: Scheme.Test(config: "Debug"),
                    profile: Scheme.Profile(config: "Debug")
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme],
                    options: .init(schemePathPrefix: "../")
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                try expect(
                    xcodeProject.pbxproj.nativeTargets
                        .contains(where: { $0.name == app.name })
                ).beTrue()
                try expect(xcscheme.launchAction?.environmentVariables) == runVariables
                try expect(xcscheme.launchAction?.storeKitConfigurationFileReference?.identifier) == "../Configuration.storekit"
                try expect(xcscheme.launchAction?.locationScenarioReference?.referenceType) == Scheme.SimulateLocation.ReferenceType.gpx.rawValue
                try expect(xcscheme.launchAction?.locationScenarioReference?.identifier) == "../File.gpx"
                try expect(xcscheme.testAction?.environmentVariables).to.beNil()
                try expect(xcscheme.profileAction?.environmentVariables).to.beNil()
            }

            $0.it("generates target schemes from config variant") {
                let configVariants = ["Test", "PreProd", "Prod"]
                var target = app
                target.scheme = TargetScheme(configVariants: configVariants)
                
                // Including here a double test for custom upper/lowercase, and dash delimited in config types
                let configs: [Config] = [
                    Config(name: "Test-Debug", type: .debug),
                    Config(name: "PreProd debug", type: .debug),
                    Config(name: "Prod-Debug", type: .debug),
                    Config(name: "Test Release", type: .release),
                    Config(name: "PreProd release", type: .release),
                    Config(name: "Prod Release", type: .release),
                ]

                let project = Project(name: "test", configs: configs, targets: [target, framework])
                let xcodeProject = try project.generateXcodeProject()

                try expect(xcodeProject.sharedData?.schemes.count) == 3
                try configVariants.forEach { variantName in
                    let xcscheme = try unwrap(xcodeProject.sharedData?.schemes
                                                .first(where: { $0.name == "\(target.name) \(variantName)" }))
                    let buildActionEntry = try unwrap(xcscheme.buildAction?.buildActionEntries.first)
                    
                    try expect((buildActionEntry.buildableReference.blueprintIdentifier?.count ?? 0) > 0) == true
                    if variantName == "PreProd" {
                        try expect(xcscheme.launchAction?.buildConfiguration) == "\(variantName) debug"
                        try expect(xcscheme.testAction?.buildConfiguration) == "\(variantName) debug"
                        try expect(xcscheme.profileAction?.buildConfiguration) == "\(variantName) release"
                        try expect(xcscheme.analyzeAction?.buildConfiguration) == "\(variantName) debug"
                        try expect(xcscheme.archiveAction?.buildConfiguration) == "\(variantName) release"
                    } else {
                        try expect(xcscheme.launchAction?.buildConfiguration) == "\(variantName)-Debug"
                        try expect(xcscheme.testAction?.buildConfiguration) == "\(variantName)-Debug"
                        try expect(xcscheme.profileAction?.buildConfiguration) == "\(variantName) Release"
                        try expect(xcscheme.analyzeAction?.buildConfiguration) == "\(variantName)-Debug"
                        try expect(xcscheme.archiveAction?.buildConfiguration) == "\(variantName) Release"
                    }
                }
            }

            $0.it("generates environment variables for target schemes") {
                let variables: [XCScheme.EnvironmentVariable] = [XCScheme.EnvironmentVariable(variable: "env", value: "var", enabled: false)]
                var target = app
                target.scheme = TargetScheme(environmentVariables: variables)

                let project = Project(name: "test", targets: [target, framework])
                let xcodeProject = try project.generateXcodeProject()

                try expect(xcodeProject.sharedData?.schemes.count) == 1

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                try expect(xcscheme.launchAction?.environmentVariables) == variables
                try expect(xcscheme.testAction?.environmentVariables) == variables
                try expect(xcscheme.profileAction?.environmentVariables) == variables
            }

            $0.it("generate scheme without debugger - run") {
                let scheme = Scheme(
                    name: "TestScheme",
                    build: Scheme.Build(targets: [buildTarget]),
                    run: Scheme.Run(config: "Debug", enableGPUFrameCaptureMode: .metal, debugEnabled: false, simulateLocation: .init(allow: true, defaultLocation: "File.gpx"), storeKitConfiguration: "Configuration.storekit")
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                try expect(xcscheme.launchAction?.selectedDebuggerIdentifier) == ""
                try expect(xcscheme.launchAction?.selectedLauncherIdentifier) == "Xcode.IDEFoundation.Launcher.PosixSpawn"
                try expect(xcscheme.launchAction?.storeKitConfigurationFileReference?.identifier) == "../../Configuration.storekit"
                try expect(xcscheme.launchAction?.locationScenarioReference?.referenceType) == Scheme.SimulateLocation.ReferenceType.gpx.rawValue
                try expect(xcscheme.launchAction?.locationScenarioReference?.identifier) == "../../File.gpx"
                try expect(xcscheme.launchAction?.enableGPUFrameCaptureMode) == .metal
            }

            $0.it("generate scheme without debugger - test") {
                let scheme = Scheme(
                    name: "TestScheme",
                    build: Scheme.Build(targets: [buildTarget]),
                    test: Scheme.Test(config: "Debug", debugEnabled: false)
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                try expect(xcscheme.testAction?.selectedDebuggerIdentifier) == ""
                try expect(xcscheme.testAction?.selectedLauncherIdentifier) == "Xcode.IDEFoundation.Launcher.PosixSpawn"
            }

            $0.it("generates pre and post actions for target schemes") {
                var target = app
                target.scheme = TargetScheme(
                    preActions: [.init(name: "Run", script: "do")],
                    postActions: [.init(name: "Run2", script: "post", settingsTarget: "MyApp")]
                )

                let project = Project(name: "test", targets: [target, framework])
                let xcodeProject = try project.generateXcodeProject()

                try expect(xcodeProject.sharedData?.schemes.count) == 1

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)

                try expect(xcscheme.buildAction?.preActions.count) == 1
                try expect(xcscheme.buildAction?.preActions.first?.title) == "Run"
                try expect(xcscheme.buildAction?.preActions.first?.scriptText) == "do"
                try expect(xcscheme.buildAction?.postActions.first?.environmentBuildable?.blueprintName) == "MyApp"

                try expect(xcscheme.launchAction?.preActions.count) == 0
                try expect(xcscheme.testAction?.postActions.count) == 0
            }

            $0.it("generates target schemes with code coverage options") {
                var target = app
                target.scheme = try TargetScheme(
                    gatherCoverageData: true,
                    coverageTargets: [
                        TestableTargetReference(framework.name),
                    ]
                )

                let project = Project(name: "test", targets: [target, framework])
                let xcodeProject = try project.generateXcodeProject()
                try expect(xcodeProject.sharedData?.schemes.count) == 1

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.codeCoverageEnabled) == true
                try expect(xcscheme.testAction?.codeCoverageTargets.count) == 1
                try expect(xcscheme.testAction?.codeCoverageTargets.first?.blueprintName) == framework.name
            }

            $0.it("generates scheme using external project file") {
                prepareXcodeProj: do {
                    let project = try! Project(path: fixturePath + "scheme_test/test_project.yml")
                    let generator = ProjectGenerator(project: project)
                    let writer = FileWriter(project: project)
                    let xcodeProject = try! generator.generateXcodeProject(userName: "someUser")
                    try! writer.writeXcodeProject(xcodeProject)
                    try! writer.writePlists()
                }
                let externalProjectPath = fixturePath + "scheme_test/TestProject.xcodeproj"
                let projectReference = ProjectReference(name: "ExternalProject", path: externalProjectPath.string)
                let target = Scheme.BuildTarget(target: .init(name: "ExternalTarget", location: .project("ExternalProject")))
                let scheme = Scheme(
                    name: "ExternalProjectScheme",
                    build: Scheme.Build(targets: [target])
                )
                let project = Project(
                    name: "test",
                    targets: [],
                    schemes: [scheme],
                    projectReferences: [projectReference]
                )
                let xcodeProject = try project.generateXcodeProject()
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.buildAction?.buildActionEntries.count) == 1
                let buildableReference = xcscheme.buildAction?.buildActionEntries.first?.buildableReference
                try expect(buildableReference?.blueprintName) == "ExternalTarget"
                try expect(buildableReference?.referencedContainer) == "container:\(externalProjectPath.string)"

            }

            $0.it("generate scheme with code coverage options") {
                prepareXcodeProj: do {
                    let project = try! Project(path: fixturePath + "scheme_test/test_project.yml")
                    let generator = ProjectGenerator(project: project)
                    let writer = FileWriter(project: project)
                    let xcodeProject = try! generator.generateXcodeProject(userName: "someUser")
                    try! writer.writeXcodeProject(xcodeProject)
                    try! writer.writePlists()
                }
                let externalProject = fixturePath + "scheme_test/TestProject.xcodeproj"
                let externalTarget = Scheme.BuildTarget(target: .init(name: "ExternalTarget", location: .project("TestProject")))
                let scheme = try Scheme(
                    name: "CodeCoverageScheme",
                    build: Scheme.Build(targets: [externalTarget]),
                    test: Scheme.Test(
                        config: "Debug",
                        gatherCoverageData: true,
                        coverageTargets: [
                            "TestProject/ExternalTarget",
                            TestableTargetReference(framework.name),
                            TestableTargetReference(name: "XcodeGenKitTests", location: .package("XcodeGen"))
                        ]
                    )
                )
                let project = Project(
                    name: "test",
                    targets: [framework],
                    schemes: [scheme],
                    packages: ["XcodeGen": .local(path: "../", group: nil, excludeFromProject: false)],
                    projectReferences: [
                        ProjectReference(name: "TestProject", path: externalProject.string),
                    ]
                )
                let xcodeProject = try project.generateXcodeProject()
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.codeCoverageEnabled) == true
                try expect(xcscheme.testAction?.codeCoverageTargets.count) == 3
                let buildableReference = xcscheme.testAction?.codeCoverageTargets.first
                try expect(buildableReference?.blueprintName) == "ExternalTarget"
                try expect(buildableReference?.referencedContainer) == "container:\(externalProject.string)"
            }

            $0.it("generates scheme with buildable product runnable for ios app target") {
                let app = Target(
                    name: "MyApp",
                    type: .application,
                    platform: .iOS,
                    scheme: TargetScheme()
                )
                let project = Project(name: "ios_test", targets: [app])
                let xcodeProject = try project.generateXcodeProject()
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.launchAction?.runnable).beOfType(XCScheme.BuildableProductRunnable.self)
            }

            $0.it("generates scheme with remote runnable for watch app target") {
                let xcscheme = try self.makeWatchScheme(appType: .watch2App, extensionType: .watch2Extension)
                try expect(xcscheme.launchAction?.runnable).beOfType(XCScheme.RemoteRunnable.self)
                try expect(xcscheme.launchAction?.storeKitConfigurationFileReference?.identifier) == "../Configuration.storekit"
            }

            $0.it("generates scheme with host target build action for watch") {
                let xcscheme = try self.makeWatchScheme(appType: .watch2App, extensionType: .watch2Extension)
                let buildEntries = xcscheme.buildAction?.buildActionEntries ?? []
                try expect(buildEntries.count) == 2
                try expect(buildEntries.first?.buildableReference.blueprintName) == "WatchApp"
                try expect(buildEntries.last?.buildableReference.blueprintName) == "HostApp"
                try expect(xcscheme.launchAction?.storeKitConfigurationFileReference?.identifier) == "../Configuration.storekit"
            }
            
            $0.it("generates scheme with extension target and specify macroExpansion") {
                let app = Target(
                    name: "MyApp",
                    type: .application,
                    platform: .iOS,
                    dependencies: [Dependency(type: .target, reference: "MyAppExtension", embed: false)]
                )

                let `extension` = Target(
                    name: "MyAppExtension",
                    type: .appExtension,
                    platform: .iOS
                )
                let appTarget = Scheme.BuildTarget(target: .local(app.name), buildTypes: [.running])
                let extensionTarget = Scheme.BuildTarget(target: .local(`extension`.name), buildTypes: [.running])
            
                let scheme = Scheme(
                    name: "TestScheme",
                    build: Scheme.Build(targets: [appTarget, extensionTarget]),
                    run: Scheme.Run(config: "Debug", macroExpansion: "MyApp")
                )
                let project = Project(
                    name: "test",
                    targets: [app, `extension`],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.macroExpansion?.buildableName) == "MyApp.app"
                try expect(xcscheme.launchAction?.macroExpansion?.buildableName) == "MyApp.app"
            }

            $0.it("allows to override test macroExpansion") {
                let app = Target(
                    name: "MyApp",
                    type: .application,
                    platform: .iOS,
                    dependencies: [Dependency(type: .target, reference: "MyAppExtension", embed: false)]
                )

                let `extension` = Target(
                    name: "MyAppExtension",
                    type: .appExtension,
                    platform: .iOS
                )
                let appTarget = Scheme.BuildTarget(target: .local(app.name), buildTypes: [.running])
                let extensionTarget = Scheme.BuildTarget(target: .local(`extension`.name), buildTypes: [.running])
            
                let scheme = Scheme(
                    name: "TestScheme",
                    build: Scheme.Build(targets: [appTarget, extensionTarget]),
                    run: Scheme.Run(config: "Debug", macroExpansion: "MyApp"),
                    test: .init(macroExpansion: "MyAppExtension")
                )
                let project = Project(
                    name: "test",
                    targets: [app, `extension`],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.macroExpansion?.buildableName) == "MyAppExtension.appex"
                try expect(xcscheme.launchAction?.macroExpansion?.buildableName) == "MyApp.app"
            }
            
            $0.it("generates scheme with macroExpansion from tests when the main target is not part of the scheme") {
                let app = Target(
                    name: "MyApp",
                    type: .application,
                    platform: .iOS,
                    dependencies: []
                )

                let mockApp = Target(
                    name: "MockApp",
                    type: .application,
                    platform: .iOS,
                    dependencies: []
                )

                let testBundle = Target(
                    name: "TestBundle",
                    type: .unitTestBundle,
                    platform: .iOS
                )
                let appTarget = Scheme.BuildTarget(target: .local(app.name), buildTypes: [.running])
                let mockAppTarget = Scheme.BuildTarget(target: .local(mockApp.name), buildTypes: [.testing])
                let testBundleTarget = Scheme.BuildTarget(target: .local(testBundle.name), buildTypes: [.testing])

                let scheme = Scheme(
                    name: "TestScheme",
                    build: Scheme.Build(targets: [appTarget, mockAppTarget, testBundleTarget]),
                    run: Scheme.Run(config: "Debug", macroExpansion: "MyApp")
                )
                let project = Project(
                    name: "test",
                    targets: [app, mockApp, testBundle],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.macroExpansion?.buildableName) == "MockApp.app"
            }

            $0.it("generates scheme with test target of local swift package") {
                let targetScheme = TargetScheme(
                    testTargets: [Scheme.Test.TestTarget(targetReference: TestableTargetReference(name: "XcodeGenKitTests", location: .package("XcodeGen")))])
                let app = Target(
                    name: "MyApp",
                    type: .application,
                    platform: .iOS,
                    dependencies: [
                        Dependency(type: .package(products: []), reference: "XcodeGen")
                    ],
                    scheme: targetScheme
                )
                let project = Project(
                    name: "ios_test",
                    targets: [app],
                    packages: ["XcodeGen": .local(path: "../", group: nil, excludeFromProject: false)]
                )
                let xcodeProject = try project.generateXcodeProject()
                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                let buildableReference = try unwrap(xcscheme.testAction?.testables.first?.buildableReference)

                try expect(buildableReference.blueprintIdentifier) == "XcodeGenKitTests"
                try expect(buildableReference.blueprintName) == "XcodeGenKitTests"
                try expect(buildableReference.buildableName) == "XcodeGenKitTests"
                try expect(buildableReference.referencedContainer) == "container:../"
            }

            $0.it("generates scheme capturing screenshots automatically and deleting on success") {
                let xcscheme = try self.makeSnapshotScheme(
                    buildTarget: buildTarget,
                    captureScreenshotsAutomatically: true,
                    deleteScreenshotsWhenEachTestSucceeds: true)

                try expect(xcscheme.testAction?.systemAttachmentLifetime).to.beNil()
            }

            $0.it("generates scheme capturing screenshots and not deleting") {
                let xcscheme = try self.makeSnapshotScheme(
                    buildTarget: buildTarget,
                    captureScreenshotsAutomatically: true,
                    deleteScreenshotsWhenEachTestSucceeds: false)

                try expect(xcscheme.testAction?.systemAttachmentLifetime) == .keepAlways
            }

            $0.it("generates scheme not capturing screenshots") {
                let xcscheme = try self.makeSnapshotScheme(
                    buildTarget: buildTarget,
                    captureScreenshotsAutomatically: false,
                    deleteScreenshotsWhenEachTestSucceeds: false)

                try expect(xcscheme.testAction?.systemAttachmentLifetime) == .keepNever
            }

            $0.it("ignores screenshot delete preference when not capturing screenshots") {
                let xcscheme = try self.makeSnapshotScheme(
                    buildTarget: buildTarget,
                    captureScreenshotsAutomatically: false,
                    deleteScreenshotsWhenEachTestSucceeds: true)

                try expect(xcscheme.testAction?.systemAttachmentLifetime) == .keepNever
            }

            $0.it("generate test plans ") {

                let testPlanPath1 = "\(fixturePath.string)/TestProject/App_iOS/App_iOS.xctestplan"
                let testPlanPath2 = "\(fixturePath.string)/TestProject/App_iOS/App_iOS.xctestplan"

                let scheme = Scheme(
                    name: "TestScheme",
                    build: Scheme.Build(targets: [buildTarget]),
                    test: Scheme.Test(config: "Debug", testPlans: [
                        .init(path: testPlanPath1, defaultPlan: false),
                        .init(path: testPlanPath2, defaultPlan: true),
                    ])
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.testPlans) == [
                    .init(reference: "container:\(testPlanPath1)", default: false),
                    .init(reference: "container:\(testPlanPath2)", default: true),
                ]
            }

            $0.it("generates scheme with screenshots as preferred screen capture format") {
                let scheme = Scheme(
                    name: "MyScheme",
                    build: Scheme.Build(targets: [buildTarget]),
                    run: Scheme.Run(config: "Debug"),
                    test: Scheme.Test(config: "Debug", preferredScreenCaptureFormat: .screenshots)
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.preferredScreenCaptureFormat) == .screenshots
            }

            $0.it("generates scheme with screen recording as preferred screen capture format") {
                let scheme = Scheme(
                    name: "MyScheme",
                    build: Scheme.Build(targets: [buildTarget]),
                    run: Scheme.Run(config: "Debug"),
                    test: Scheme.Test(config: "Debug", preferredScreenCaptureFormat: .screenRecording)
                )
                let project = Project(
                    name: "test",
                    targets: [app, framework],
                    schemes: [scheme]
                )
                let xcodeProject = try project.generateXcodeProject()

                let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
                try expect(xcscheme.testAction?.preferredScreenCaptureFormat) == .screenRecording
            }
        }
    }
    
    func testOverrideLastUpgradeVersionWhenUserDidSpecify() throws {
        var target = app
        target.scheme = TargetScheme()
        
        let lastUpgradeKey = "LastUpgradeCheck"
        let lastUpgradeValue = "1234"
        let attributes: [String: Any] = [lastUpgradeKey: lastUpgradeValue]
        let project = Project(name: "test", targets: [target, framework], attributes: attributes)
        let xcodeProject = try project.generateXcodeProject()

        let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
        XCTAssertEqual(xcscheme.lastUpgradeVersion, lastUpgradeValue)
    }


    func testDefaultLastUpgradeVersionWhenUserDidNotSpecify() throws {
        var target = app
        target.scheme = TargetScheme()

        let project = Project(name: "test", targets: [target, framework])
        let xcodeProject = try project.generateXcodeProject()

        let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
        XCTAssertEqual(xcscheme.lastUpgradeVersion, project.xcodeVersion)
    }

    func testGenerateSchemeManagementOnHiddenTargetScheme() throws {
        var target = app
        target.scheme = TargetScheme(management: Scheme.Management(isShown: false))

        let project = Project(name: "test", targets: [target, framework])
        let xcodeProject = try project.generateXcodeProject()

        let xcSchemeManagement = try XCTUnwrap(xcodeProject.userData.first?.schemeManagement)
        XCTAssertEqual(xcSchemeManagement.schemeUserState![0].name, "MyApp.xcscheme")
        XCTAssertEqual(xcSchemeManagement.schemeUserState![0].shared, true)
        XCTAssertEqual(xcSchemeManagement.schemeUserState![0].isShown, false)
        XCTAssertEqual(xcSchemeManagement.schemeUserState![0].orderHint, nil)
    }

    // MARK: - Helpers

    private func makeWatchScheme(appType: PBXProductType, extensionType: PBXProductType) throws -> XCScheme {
        let watchExtension = Target(
            name: "WatchExtension",
            type: extensionType,
            platform: .watchOS
        )
        let watchApp = Target(
            name: "WatchApp",
            type: appType,
            platform: .watchOS,
            dependencies: [Dependency(type: .target, reference: watchExtension.name)],
            scheme: TargetScheme(storeKitConfiguration: "Configuration.storekit")
        )
        let hostApp = Target(
            name: "HostApp",
            type: .application,
            platform: .iOS,
            dependencies: [Dependency(type: .target, reference: watchApp.name)]
        )
        let project = Project(
            name: "watch_test",
            targets: [hostApp, watchApp, watchExtension],
            options: .init(schemePathPrefix: "../")
        )
        let xcodeProject = try project.generateXcodeProject()
        return try unwrap(xcodeProject.sharedData?.schemes.first)
    }

    private func makeSnapshotScheme(buildTarget: Scheme.BuildTarget, captureScreenshotsAutomatically: Bool, deleteScreenshotsWhenEachTestSucceeds: Bool) throws -> XCScheme {
        let scheme = Scheme(
            name: "MyScheme",
            build: Scheme.Build(targets: [buildTarget]),
            run: Scheme.Run(config: "Debug"),
            test: Scheme.Test(config: "Debug", captureScreenshotsAutomatically: captureScreenshotsAutomatically, deleteScreenshotsWhenEachTestSucceeds: deleteScreenshotsWhenEachTestSucceeds)
        )
        let project = Project(
            name: "test",
            targets: [app, framework],
            schemes: [scheme]
        )
        let xcodeProject = try project.generateXcodeProject()
        return try unwrap(xcodeProject.sharedData?.schemes.first)
    }
}
