import Foundation
import JSONUtilities
import XcodeProj

public struct TargetScheme: Equatable {
    public static let gatherCoverageDataDefault = false
    public static let disableMainThreadCheckerDefault = false
    public static let stopOnEveryMainThreadCheckerIssueDefault = false
    public static let disableThreadPerformanceCheckerDefault = false
    public static let buildImplicitDependenciesDefault = true

    public var testTargets: [Scheme.Test.TestTarget]
    public var configVariants: [String]
    public var gatherCoverageData: Bool
    public var coverageTargets: [TestableTargetReference]
    public var storeKitConfiguration: String?
    public var language: String?
    public var region: String?
    public var disableMainThreadChecker: Bool
    public var stopOnEveryMainThreadCheckerIssue: Bool
    public var disableThreadPerformanceChecker: Bool
    public var buildImplicitDependencies: Bool
    public var commandLineArguments: [String: Bool]
    public var environmentVariables: [XCScheme.EnvironmentVariable]
    public var preActions: [Scheme.ExecutionAction]
    public var postActions: [Scheme.ExecutionAction]
    public var management: Scheme.Management?
    public var testPlans: [TestPlan]

    public init(
        testTargets: [Scheme.Test.TestTarget] = [],
        testPlans: [TestPlan] = [],
        configVariants: [String] = [],
        gatherCoverageData: Bool = gatherCoverageDataDefault,
        coverageTargets: [TestableTargetReference] = [],
        storeKitConfiguration: String? = nil,
        language: String? = nil,
        region: String? = nil,
        disableMainThreadChecker: Bool = disableMainThreadCheckerDefault,
        stopOnEveryMainThreadCheckerIssue: Bool = stopOnEveryMainThreadCheckerIssueDefault,
        disableThreadPerformanceChecker: Bool = disableThreadPerformanceCheckerDefault,
        buildImplicitDependencies: Bool = buildImplicitDependenciesDefault,
        commandLineArguments: [String: Bool] = [:],
        environmentVariables: [XCScheme.EnvironmentVariable] = [],
        preActions: [Scheme.ExecutionAction] = [],
        postActions: [Scheme.ExecutionAction] = [],
        management: Scheme.Management? = nil
    ) {
        self.testTargets = testTargets
        self.testPlans = testPlans
        self.configVariants = configVariants
        self.gatherCoverageData = gatherCoverageData
        self.coverageTargets = coverageTargets
        self.storeKitConfiguration = storeKitConfiguration
        self.language = language
        self.region = region
        self.disableMainThreadChecker = disableMainThreadChecker
        self.stopOnEveryMainThreadCheckerIssue = stopOnEveryMainThreadCheckerIssue
        self.disableThreadPerformanceChecker = disableThreadPerformanceChecker
        self.buildImplicitDependencies = buildImplicitDependencies
        self.commandLineArguments = commandLineArguments
        self.environmentVariables = environmentVariables
        self.preActions = preActions
        self.postActions = postActions
        self.postActions = postActions
        self.management = management
    }
}

extension TargetScheme: JSONObjectConvertible {

    public init(jsonDictionary: JSONDictionary) throws {
        if let targets = jsonDictionary["testTargets"] as? [Any] {
            testTargets = try targets.compactMap { target in
                if let string = target as? String {
                    return .init(targetReference: try TestableTargetReference(string))
                } else if let dictionary = target as? JSONDictionary,
                          let target: Scheme.Test.TestTarget = try? .init(jsonDictionary: dictionary) {
                    return target
                } else {
                    return nil
                }
            }
        } else {
            testTargets = []
        }

        if let targets = jsonDictionary["coverageTargets"] as? [Any] {
            coverageTargets = try targets.compactMap { target in
                if let string = target as? String {
                    return try TestableTargetReference(string)
                } else if let dictionary = target as? JSONDictionary,
                          let target: TestableTargetReference = try? .init(jsonDictionary: dictionary) {
                    return target
                } else {
                    return nil
                }
            }
        } else {
            coverageTargets = []
        }

        testPlans = try (jsonDictionary.json(atKeyPath: "testPlans") ?? []).map { try TestPlan(jsonDictionary: $0) }
        configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? []
        gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? TargetScheme.gatherCoverageDataDefault
        storeKitConfiguration = jsonDictionary.json(atKeyPath: "storeKitConfiguration")
        language = jsonDictionary.json(atKeyPath: "language")
        region = jsonDictionary.json(atKeyPath: "region")
        disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? TargetScheme.disableMainThreadCheckerDefault
        stopOnEveryMainThreadCheckerIssue = jsonDictionary.json(atKeyPath: "stopOnEveryMainThreadCheckerIssue") ?? TargetScheme.stopOnEveryMainThreadCheckerIssueDefault
        disableThreadPerformanceChecker = jsonDictionary.json(atKeyPath: "disableThreadPerformanceChecker") ?? TargetScheme.disableThreadPerformanceCheckerDefault
        buildImplicitDependencies = jsonDictionary.json(atKeyPath: "buildImplicitDependencies") ?? TargetScheme.buildImplicitDependenciesDefault
        commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:]
        environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary)
        preActions = jsonDictionary.json(atKeyPath: "preActions") ?? []
        postActions = jsonDictionary.json(atKeyPath: "postActions") ?? []
        management = jsonDictionary.json(atKeyPath: "management")
    }
}

extension TargetScheme: JSONEncodable {
    public func toJSONValue() -> Any {
        var dict: [String: Any] = [
            "configVariants": configVariants,
            "coverageTargets": coverageTargets.map { $0.reference },
            "commandLineArguments": commandLineArguments,
            "testTargets": testTargets.map { $0.toJSONValue() },
            "testPlans": testPlans.map { $0.toJSONValue() },
            "environmentVariables": environmentVariables.map { $0.toJSONValue() },
            "preActions": preActions.map { $0.toJSONValue() },
            "postActions": postActions.map { $0.toJSONValue() },
        ]

        if gatherCoverageData != TargetScheme.gatherCoverageDataDefault {
            dict["gatherCoverageData"] = gatherCoverageData
        }

        if let storeKitConfiguration = storeKitConfiguration {
            dict["storeKitConfiguration"] = storeKitConfiguration
        }

        if disableMainThreadChecker != TargetScheme.disableMainThreadCheckerDefault {
            dict["disableMainThreadChecker"] = disableMainThreadChecker
        }

        if stopOnEveryMainThreadCheckerIssue != TargetScheme.stopOnEveryMainThreadCheckerIssueDefault {
            dict["stopOnEveryMainThreadCheckerIssue"] = stopOnEveryMainThreadCheckerIssue
        }

        if disableThreadPerformanceChecker != TargetScheme.disableThreadPerformanceCheckerDefault {
            dict["disableThreadPerformanceChecker"] = disableThreadPerformanceChecker
        }

        if buildImplicitDependencies != TargetScheme.buildImplicitDependenciesDefault {
            dict["buildImplicitDependencies"] = buildImplicitDependencies
        }

        if let language = language {
            dict["language"] = language
        }

        if let region = region {
            dict["region"] = region
        }

        if let management = management {
            dict["management"] = management.toJSONValue()
        }

        return dict
    }
}

extension TargetScheme: PathContainer {

    static var pathProperties: [PathProperty] {
        [
            .object("testPlans", TestPlan.pathProperties),
        ]
    }
}
