import CodexBarCore
import Commander
import Foundation

extension CodexBarCLI {
    static func runConfig(path: [String], values: ParsedValues) {
        switch path {
        case ["config", "validate"]:
            self.runConfigValidate(values)
        case ["config", "dump"]:
            self.runConfigDump(values)
        case ["config", "providers"]:
            self.runConfigProviders(values)
        case ["config", "enable"]:
            self.runConfigSetProviderEnabled(values, enabled: true)
        case ["config", "disable"]:
            self.runConfigSetProviderEnabled(values, enabled: false)
        case ["config", "set-api-key"]:
            self.runConfigSetAPIKey(values)
        default:
            self.exit(
                code: .failure,
                message: "Unknown command",
                output: CLIOutputPreferences.from(values: values),
                kind: .args)
        }
    }

    static func runConfigValidate(_ values: ParsedValues) {
        let output = CLIOutputPreferences.from(values: values)
        let config = Self.loadConfig(output: output)
        let issues = CodexBarConfigValidator.validate(config)
        let hasErrors = issues.contains(where: { $0.severity == .error })

        switch output.format {
        case .text:
            if issues.isEmpty {
                print("Config: OK")
            } else {
                for issue in issues {
                    let provider = issue.provider?.rawValue ?? "config"
                    let field = issue.field ?? ""
                    let prefix = "[\(issue.severity.rawValue.uppercased())]"
                    let suffix = field.isEmpty ? "" : " (\(field))"
                    print("\(prefix) \(provider)\(suffix): \(issue.message)")
                }
            }
        case .json:
            Self.printJSON(issues, pretty: output.pretty)
        }

        Self.exit(code: hasErrors ? .failure : .success, output: output, kind: .config)
    }

    static func runConfigDump(_ values: ParsedValues) {
        let output = CLIOutputPreferences.from(values: values)
        let showSecrets = values.flags.contains("showSecrets")
        let config = Self.loadConfig(output: output).sanitizedForDump(showSecrets: showSecrets)
        Self.printJSON(config, pretty: output.pretty)
        Self.exit(code: .success, output: output, kind: .config)
    }

    static func runConfigProviders(_ values: ParsedValues) {
        let output = CLIOutputPreferences.from(values: values)
        let config = Self.loadConfig(output: output)
        let results = Self.configProviderStatuses(config)

        switch output.format {
        case .text:
            for result in results {
                let state = result.enabled ? "enabled" : "disabled"
                let marker = result.defaultEnabled ? " default" : ""
                print("\(result.provider): \(state)\(marker) (\(result.displayName))")
            }
        case .json:
            Self.printJSON(results, pretty: output.pretty)
        }

        Self.exit(code: .success, output: output, kind: .config)
    }

    static func runConfigSetProviderEnabled(_ values: ParsedValues, enabled: Bool) {
        let output = CLIOutputPreferences.from(values: values)
        guard let rawProvider = values.options["provider"]?.last,
              let provider = ProviderDescriptorRegistry.cliNameMap[rawProvider.lowercased()]
        else {
            Self.exit(
                code: .failure,
                message: "Unknown or missing provider. Use --provider <name>.",
                output: output,
                kind: .args)
        }

        let store = CodexBarConfigStore()
        var config = Self.loadConfig(output: output)
        config = Self.configSettingProviderEnabled(config, provider: provider, enabled: enabled)

        do {
            try store.save(config)
        } catch {
            Self.exit(code: .failure, message: error.localizedDescription, output: output, kind: .config)
        }

        let metadata = ProviderDescriptorRegistry.descriptor(for: provider).metadata
        let result = ConfigProviderToggleResult(
            provider: provider.rawValue,
            displayName: metadata.displayName,
            enabled: enabled,
            configPath: store.fileURL.path)

        switch output.format {
        case .text:
            let state = enabled ? "enabled" : "disabled"
            print("Config: \(state) \(metadata.displayName)")
        case .json:
            Self.printJSON(result, pretty: output.pretty)
        }

        Self.exit(code: .success, output: output, kind: .config)
    }

    static func unsupportedAPIKeyErrorMessage(for provider: UsageProvider, rawProvider: String) -> String {
        // Provider-specific by design: Codex users are redirected to the separate OpenAI Platform provider ID.
        if provider == .codex {
            "\(rawProvider) does not support config API keys. For OpenAI Platform API keys, use '--provider openai'."
        } else {
            "\(rawProvider) does not support config API keys."
        }
    }

    static func runConfigSetAPIKey(_ values: ParsedValues) {
        let output = CLIOutputPreferences.from(values: values)

        guard let rawProvider = values.options["provider"]?.last,
              let provider = ProviderDescriptorRegistry.cliNameMap[rawProvider.lowercased()]
        else {
            Self.exit(
                code: .failure,
                message: "Unknown or missing provider. Use --provider <name>.",
                output: output,
                kind: .args)
        }
        guard ProviderConfigEnvironment.supportsAPIKeyOverride(for: provider) else {
            Self.exit(
                code: .failure,
                message: Self.unsupportedAPIKeyErrorMessage(for: provider, rawProvider: rawProvider),
                output: output,
                kind: .args)
        }

        let apiKey: String
        do {
            apiKey = try Self.resolveConfigAPIKeyInput(
                apiKey: values.options["apiKey"]?.last,
                readFromStdin: values.flags.contains("stdin"))
        } catch {
            Self.exit(code: .failure, message: error.localizedDescription, output: output, kind: .args)
        }

        let enableProvider = !values.flags.contains("noEnable")
        let store = CodexBarConfigStore()
        var config = Self.loadConfig(output: output)
        let accountOptions: ConfigAPIKeyAccountOptions?
        do {
            accountOptions = try Self.resolveConfigAPIKeyAccountOptions(
                provider: provider,
                label: values.options["label"]?.last,
                usageScope: values.options["usageScope"]?.last,
                organizationID: values.options["organizationId"]?.last,
                workspaceID: values.options["workspaceId"]?.last)
        } catch {
            Self.exit(code: .failure, message: error.localizedDescription, output: output, kind: .args)
        }
        config = Self.configSettingAPIKey(
            config,
            provider: provider,
            apiKey: apiKey,
            enableProvider: enableProvider,
            accountOptions: accountOptions)

        do {
            try store.save(config)
        } catch {
            Self.exit(code: .failure, message: error.localizedDescription, output: output, kind: .config)
        }

        let result = ConfigSetAPIKeyResult(
            provider: provider.rawValue,
            enabled: config.providerConfig(for: provider.instanceID)?.enabled ?? false,
            configPath: store.fileURL.path)

        switch output.format {
        case .text:
            let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName
            let suffix = result.enabled ? " and enabled" : ""
            let action = accountOptions == nil ? "stored API key" : "stored team token account"
            print("Config: \(action) for \(name)\(suffix)")
        case .json:
            Self.printJSON(result, pretty: output.pretty)
        }

        Self.exit(code: .success, output: output, kind: .config)
    }

    static func resolveConfigAPIKeyInput(apiKey: String?, readFromStdin: Bool) throws -> String {
        if apiKey != nil, readFromStdin {
            throw CLIArgumentError("Use either --api-key or --stdin, not both.")
        }

        let raw: String? = if readFromStdin {
            String(data: FileHandle.standardInput.readDataToEndOfFile(), encoding: .utf8)
        } else {
            apiKey
        }

        guard let value = Self.cleanConfigSecret(raw) else {
            throw CLIArgumentError("Missing API key. Pass --api-key <key> or pipe it with --stdin.")
        }
        return value
    }

    static func configSettingAPIKey(
        _ config: CodexBarConfig,
        provider: UsageProvider,
        apiKey: String,
        enableProvider: Bool,
        accountOptions: ConfigAPIKeyAccountOptions? = nil) -> CodexBarConfig
    {
        var updated = config.normalized()
        var providerConfig = updated.providerConfig(for: provider.instanceID) ?? ProviderConfig(id: provider.instanceID)
        if let accountOptions {
            let existing = providerConfig.tokenAccounts
            let accounts = existing?.accounts ?? []
            let account = ProviderTokenAccount(
                id: UUID(),
                label: accountOptions.label,
                token: apiKey,
                addedAt: Date().timeIntervalSince1970,
                lastUsed: nil,
                usageScope: accountOptions.usageScope.rawValue,
                organizationID: accountOptions.organizationID,
                workspaceID: accountOptions.workspaceID)
            providerConfig.tokenAccounts = ProviderTokenAccountData(
                version: existing?.version ?? 1,
                accounts: accounts + [account],
                activeIndex: accounts.count)
            providerConfig.apiKey = nil
            if enableProvider {
                providerConfig.enabled = true
            }
            updated.setProviderConfig(providerConfig)
            return updated
        }
        providerConfig.apiKey = apiKey
        // Provider-specific by design: legacy Moonshot config binds a newly set key to its existing/default region.
        if provider == .moonshot {
            providerConfig.apiKeyRegion = providerConfig.sanitizedRegion ?? MoonshotRegion.international.rawValue
        }
        if enableProvider {
            providerConfig.enabled = true
        }
        updated.setProviderConfig(providerConfig)
        return updated
    }

    static func resolveConfigAPIKeyAccountOptions(
        provider: UsageProvider,
        label: String?,
        usageScope: String?,
        organizationID: String?,
        workspaceID: String?) throws -> ConfigAPIKeyAccountOptions?
    {
        let cleanedLabel = Self.cleanConfigValue(label)
        let cleanedScope = Self.cleanConfigValue(usageScope)
        let cleanedOrganizationID = try Self.cleanSingleLineConfigValue(
            organizationID,
            fieldName: "organization-id")
        let cleanedWorkspaceID = try Self.cleanSingleLineConfigValue(
            workspaceID,
            fieldName: "workspace-id")
        let hasAccountOptions = cleanedLabel != nil ||
            cleanedScope != nil ||
            cleanedOrganizationID != nil ||
            cleanedWorkspaceID != nil
        guard hasAccountOptions else { return nil }

        // Provider-specific by design: z.ai team tokens alone accept organization, workspace, and usage-scope fields.
        guard provider == .zai else {
            throw CLIArgumentError("Token-account options are only supported for --provider zai.")
        }

        guard cleanedScope?.lowercased() == ZaiUsageScope.team.rawValue else {
            throw CLIArgumentError("Use --usage-scope team for z.ai team accounts, or omit account options.")
        }
        guard let organizationID = cleanedOrganizationID else {
            throw CLIArgumentError("Missing --organization-id for z.ai team usage.")
        }
        guard let workspaceID = cleanedWorkspaceID else {
            throw CLIArgumentError("Missing --workspace-id for z.ai team usage.")
        }

        return ConfigAPIKeyAccountOptions(
            label: cleanedLabel ?? "Team",
            usageScope: .team,
            organizationID: organizationID,
            workspaceID: workspaceID)
    }

    static func configSettingProviderEnabled(
        _ config: CodexBarConfig,
        provider: UsageProvider,
        enabled: Bool) -> CodexBarConfig
    {
        var updated = config.normalized()
        var providerConfig = updated.providerConfig(for: provider.instanceID) ?? ProviderConfig(id: provider.instanceID)
        providerConfig.enabled = enabled
        updated.setProviderConfig(providerConfig)
        return updated
    }

    static func configProviderStatuses(_ config: CodexBarConfig) -> [ConfigProviderStatusResult] {
        let metadata = ProviderDescriptorRegistry.metadata
        return config.normalized().providers.map { providerConfig in
            let provider = providerConfig.id.firstPartyProvider
            let meta = provider.flatMap { metadata[$0] }
            let defaultEnabled = meta?.defaultEnabled ?? false
            return ConfigProviderStatusResult(
                provider: providerConfig.id.rawValue,
                displayName: meta?.displayName ?? providerConfig.id.rawValue,
                enabled: providerConfig.enabled ?? defaultEnabled,
                defaultEnabled: defaultEnabled)
        }
    }

    private static func cleanConfigSecret(_ raw: String?) -> String? {
        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
            return nil
        }
        if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
            (value.hasPrefix("'") && value.hasSuffix("'"))
        {
            value = String(value.dropFirst().dropLast())
        }
        value = value.trimmingCharacters(in: .whitespacesAndNewlines)
        return value.isEmpty ? nil : value
    }

    private static func cleanConfigValue(_ raw: String?) -> String? {
        guard let value = self.cleanConfigSecret(raw) else { return nil }
        return value
    }

    private static func cleanSingleLineConfigValue(_ raw: String?, fieldName: String) throws -> String? {
        guard let value = self.cleanConfigValue(raw) else { return nil }
        guard !value.contains(where: \.isNewline) else {
            throw CLIArgumentError("--\(fieldName) must be a single line.")
        }
        return value
    }
}

struct ConfigAPIKeyAccountOptions: Equatable {
    let label: String
    let usageScope: ZaiUsageScope
    let organizationID: String
    let workspaceID: String
}

struct ConfigOptions: CommanderParsable {
    @Flag(names: [.short("v"), .long("verbose")], help: "Enable verbose logging")
    var verbose: Bool = false

    @Flag(name: .long("json-output"), help: "Emit machine-readable logs")
    var jsonOutput: Bool = false

    @Option(name: .long("log-level"), help: "Set log level (trace|verbose|debug|info|warning|error|critical)")
    var logLevel: String?

    @Option(name: .long("format"), help: "Output format: text | json")
    var format: OutputFormat?

    @Flag(name: .long("json"), help: "")
    var jsonShortcut: Bool = false

    @Flag(name: .long("json-only"), help: "Emit JSON only (suppress non-JSON output)")
    var jsonOnly: Bool = false

    @Flag(name: .long("pretty"), help: "Pretty-print JSON output")
    var pretty: Bool = false
}

struct ConfigDumpOptions: CommanderParsable {
    @Flag(names: [.short("v"), .long("verbose")], help: "Enable verbose logging")
    var verbose: Bool = false

    @Flag(name: .long("json-output"), help: "Emit machine-readable logs")
    var jsonOutput: Bool = false

    @Option(name: .long("log-level"), help: "Set log level (trace|verbose|debug|info|warning|error|critical)")
    var logLevel: String?

    @Option(name: .long("format"), help: "Output format: text | json")
    var format: OutputFormat?

    @Flag(name: .long("json"), help: "")
    var jsonShortcut: Bool = false

    @Flag(name: .long("json-only"), help: "Emit JSON only (suppress non-JSON output)")
    var jsonOnly: Bool = false

    @Flag(name: .long("pretty"), help: "Pretty-print JSON output")
    var pretty: Bool = false

    @Flag(name: .long("show-secrets"), help: "Include raw un-redacted API keys and tokens in output")
    var showSecrets: Bool = false
}

struct ConfigSetAPIKeyOptions: CommanderParsable {
    @Flag(names: [.short("v"), .long("verbose")], help: "Enable verbose logging")
    var verbose: Bool = false

    @Flag(name: .long("json-output"), help: "Emit machine-readable logs")
    var jsonOutput: Bool = false

    @Option(name: .long("log-level"), help: "Set log level (trace|verbose|debug|info|warning|error|critical)")
    var logLevel: String?

    @Option(name: .long("format"), help: "Output format: text | json")
    var format: OutputFormat?

    @Flag(name: .long("json"), help: "")
    var jsonShortcut: Bool = false

    @Flag(name: .long("json-only"), help: "Emit JSON only (suppress non-JSON output)")
    var jsonOnly: Bool = false

    @Flag(name: .long("pretty"), help: "Pretty-print JSON output")
    var pretty: Bool = false

    @Option(name: .long("provider"), help: ProviderHelp.optionHelp)
    var provider: String?

    @Option(name: .long("api-key"), help: "API key to store")
    var apiKey: String?

    @Flag(name: .long("stdin"), help: "Read API key from stdin")
    var stdin: Bool = false

    @Flag(name: .long("no-enable"), help: "Store the key without enabling the provider")
    var noEnable: Bool = false

    @Option(name: .long("label"), help: "Token-account label (z.ai team mode)")
    var label: String?

    @Option(name: .long("usage-scope"), help: "Token-account usage scope (z.ai: team)")
    var usageScope: String?

    @Option(name: .long("organization-id"), help: "z.ai BigModel organization ID for team usage")
    var organizationId: String?

    @Option(name: .long("workspace-id"), help: "z.ai BigModel project ID for team usage")
    var workspaceId: String?
}

struct ConfigProviderToggleOptions: CommanderParsable {
    @Flag(names: [.short("v"), .long("verbose")], help: "Enable verbose logging")
    var verbose: Bool = false

    @Flag(name: .long("json-output"), help: "Emit machine-readable logs")
    var jsonOutput: Bool = false

    @Option(name: .long("log-level"), help: "Set log level (trace|verbose|debug|info|warning|error|critical)")
    var logLevel: String?

    @Option(name: .long("format"), help: "Output format: text | json")
    var format: OutputFormat?

    @Flag(name: .long("json"), help: "")
    var jsonShortcut: Bool = false

    @Flag(name: .long("json-only"), help: "Emit JSON only (suppress non-JSON output)")
    var jsonOnly: Bool = false

    @Flag(name: .long("pretty"), help: "Pretty-print JSON output")
    var pretty: Bool = false

    @Option(name: .long("provider"), help: ProviderHelp.optionHelp)
    var provider: String?
}

private struct ConfigSetAPIKeyResult: Encodable {
    let provider: String
    let enabled: Bool
    let configPath: String
}

struct ConfigProviderStatusResult: Encodable, Equatable {
    let provider: String
    let displayName: String
    let enabled: Bool
    let defaultEnabled: Bool
}

private struct ConfigProviderToggleResult: Encodable {
    let provider: String
    let displayName: String
    let enabled: Bool
    let configPath: String
}
