import Foundation

public struct APITokenFetchStrategy: ProviderFetchStrategy {
    public typealias TokenResolver = @Sendable ([String: String]) -> String?
    public typealias MissingCredentialsError = @Sendable () -> (any Error & Sendable)
    public typealias UsageLoader = @Sendable (String, ProviderFetchContext) async throws -> UsageSnapshot

    public let id: String
    public let kind: ProviderFetchKind = .apiToken

    private let sourceLabel: String
    private let reportsMissingCredentials: Bool
    private let resolveToken: TokenResolver
    private let missingCredentialsError: MissingCredentialsError
    private let loadUsage: UsageLoader

    public init(
        id: String,
        sourceLabel: String = "api",
        reportsMissingCredentials: Bool = false,
        resolveToken: @escaping TokenResolver,
        missingCredentialsError: @escaping MissingCredentialsError,
        loadUsage: @escaping UsageLoader)
    {
        self.id = id
        self.sourceLabel = sourceLabel
        self.reportsMissingCredentials = reportsMissingCredentials
        self.resolveToken = resolveToken
        self.missingCredentialsError = missingCredentialsError
        self.loadUsage = loadUsage
    }

    public func isAvailable(_ context: ProviderFetchContext) async -> Bool {
        self.reportsMissingCredentials || self.resolveToken(context.env) != nil
    }

    public func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
        guard let token = self.resolveToken(context.env) else {
            throw self.missingCredentialsError()
        }
        return try await self.makeResult(
            usage: self.loadUsage(token, context),
            sourceLabel: self.sourceLabel)
    }

    public func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
        false
    }
}

extension ProviderFetchPlan {
    public static func apiToken(
        strategyID: String,
        sourceLabel: String = "api",
        reportsMissingCredentials: Bool = false,
        resolveToken: @escaping APITokenFetchStrategy.TokenResolver,
        missingCredentialsError: @escaping APITokenFetchStrategy.MissingCredentialsError,
        loadUsage: @escaping APITokenFetchStrategy.UsageLoader) -> ProviderFetchPlan
    {
        ProviderFetchPlan(
            sourceModes: [.auto, .api],
            pipeline: ProviderFetchPipeline(resolveStrategies: { _ in
                [APITokenFetchStrategy(
                    id: strategyID,
                    sourceLabel: sourceLabel,
                    reportsMissingCredentials: reportsMissingCredentials,
                    resolveToken: resolveToken,
                    missingCredentialsError: missingCredentialsError,
                    loadUsage: loadUsage)]
            }))
    }
}
