import Foundation
import AlignerCore

struct Round015EntitlementLaunchOptions {
    let fixtureState: EntitlementFixtureState?
    let now: Date?
    let trialStartedAt: Date?
    let lastSuccessfulVerifyAt: Date?
    let verifyResult: VerifyResult?
    let usesEphemeralLicenseStore: Bool
    let debugLicenseKey: DebugLicenseKey?

    enum VerifyResult: String {
        case success
        case networkFailure
        case invalid
        case revoked
        case serverError
        case timeout
    }

    enum DebugLicenseKey: String {
        case valid
        case invalid
        case revoked
    }

    static func parse(arguments: [String]) -> Round015EntitlementLaunchOptions {
        Round015EntitlementLaunchOptions(
            fixtureState: stringValue(for: "--round015-license-state", in: arguments)
                .flatMap(EntitlementFixtureState.init(rawValue:)),
            now: dateValue(for: "--round015-now", in: arguments),
            trialStartedAt: dateValue(for: "--round015-trial-started-at", in: arguments),
            lastSuccessfulVerifyAt: dateValue(for: "--round015-last-successful-verify-at", in: arguments),
            verifyResult: stringValue(for: "--round015-verify-result", in: arguments)
                .flatMap(VerifyResult.init(rawValue:)),
            usesEphemeralLicenseStore: arguments.contains("--round015-ephemeral-license-store"),
            debugLicenseKey: stringValue(for: "--round015-debug-license-key", in: arguments)
                .flatMap(DebugLicenseKey.init(rawValue:))
        )
    }

    func authorizationReportDictionary() -> [String: Any] {
        guard let snapshot else {
            return [
                "state": "notConfigured",
                "proFeature": ProFeature.windowShortcutActivation.rawValue,
                "proFeatureUnlocked": false,
                "fixtureEnabled": false,
                "ephemeralLicenseStore": usesEphemeralLicenseStore,
                "licenseKeyPresent": debugLicenseKey != nil,
                "licenseKeyFingerprint": debugLicenseKey.map { DevelopmentDiagnostics.stableFingerprint($0.rawValue) } ?? NSNull()
            ]
        }

        let evaluationNow = effectiveNow
        let entitlement = EntitlementPolicy.evaluate(
            snapshot: snapshot,
            feature: .windowShortcutActivation,
            now: evaluationNow
        )

        var report: [String: Any] = [
            "state": stateLabel(entitlement),
            "proFeature": ProFeature.windowShortcutActivation.rawValue,
            "proFeatureUnlocked": entitlement.isProAllowed,
            "decision": decisionLabel(entitlement),
            "decisionReason": decisionReasonLabel(entitlement),
            "fixtureEnabled": fixtureState != nil,
            "fixtureState": fixtureState?.rawValue ?? NSNull(),
            "ephemeralLicenseStore": usesEphemeralLicenseStore,
            "now": Self.string(from: evaluationNow),
            "trialStartedAt": trialStartedAtString(from: snapshot.trialState) ?? NSNull(),
            "trialEndsAt": trialEndsAtString(from: snapshot.trialState) ?? NSNull(),
            "trialDaysRemaining": trialDaysRemaining(from: snapshot.trialState, now: evaluationNow) ?? NSNull(),
            "lastVerifyResult": lastVerifyResultLabel(from: snapshot.validationFreshness),
            "lastSuccessfulVerifyAt": lastSuccessfulVerifyAtString(from: snapshot.validationFreshness) ?? NSNull(),
            "offlineGraceEndsAt": offlineGraceEndsAtString(from: snapshot.validationFreshness) ?? NSNull(),
            "offlineGraceRemainingDays": offlineGraceRemainingDays(from: snapshot.validationFreshness, now: evaluationNow) ?? NSNull(),
            "licenseKeyPresent": debugLicenseKey != nil || licenseKeyPresent(in: snapshot.licenseState),
            "licenseKeyFingerprint": licenseFingerprint(from: snapshot.licenseState)
                ?? debugLicenseKey.map { DevelopmentDiagnostics.stableFingerprint($0.rawValue) }
                ?? NSNull()
        ]

        if let verifyResult {
            report["fixtureVerifyResult"] = verifyResult.rawValue
        }
        if let debugLicenseKey {
            report["debugLicenseKeyKind"] = debugLicenseKey.rawValue
        }

        return report
    }

    private var effectiveNow: Date {
        now ?? Date()
    }

    private var snapshot: EntitlementSnapshot? {
        guard fixtureState != nil
                || trialStartedAt != nil
                || lastSuccessfulVerifyAt != nil
                || verifyResult != nil
                || debugLicenseKey != nil
        else {
            return nil
        }

        if fixtureState == nil {
            return snapshotFromOverrides(base: EntitlementFixtureState.trialExpired)
        }

        return snapshotFromOverrides(base: fixtureState ?? .trialExpired)
    }

    private func snapshotFromOverrides(base fixtureState: EntitlementFixtureState) -> EntitlementSnapshot {
        var snapshot = FixtureEntitlementProvider(
            fixtureState: fixtureState,
            now: effectiveNow
        ).snapshot

        if let trialStartedAt {
            snapshot.trialState = trialState(
                from: snapshot.trialState,
                startedAt: trialStartedAt,
                now: effectiveNow
            )
        }

        if let debugLicenseKey {
            snapshot.licenseState = licenseState(for: debugLicenseKey)
        }

        if let verifyResult {
            snapshot.validationFreshness = validationFreshness(
                for: verifyResult,
                now: effectiveNow,
                lastSuccessfulVerifyAt: lastSuccessfulVerifyAt
            )
        } else if let lastSuccessfulVerifyAt {
            snapshot.validationFreshness = .valid(
                lastValidAt: lastSuccessfulVerifyAt,
                nextCheckAt: lastSuccessfulVerifyAt.addingTimeInterval(24 * 60 * 60)
            )
        }

        return snapshot
    }

    private func trialState(from base: TrialState, startedAt: Date, now: Date) -> TrialState {
        let expiresAt = startedAt.addingTimeInterval(EntitlementPolicy.defaultTrialDuration)
        switch base {
        case .expired:
            return now < expiresAt
                ? .active(startedAt: startedAt, expiresAt: expiresAt)
                : .expired(startedAt: startedAt, expiredAt: expiresAt)
        default:
            return .active(startedAt: startedAt, expiresAt: expiresAt)
        }
    }

    private func licenseState(for debugLicenseKey: DebugLicenseKey) -> LicenseState {
        switch debugLicenseKey {
        case .valid:
            return .activated(
                fingerprint: DevelopmentDiagnostics.stableFingerprint("round015-valid"),
                instanceID: "round015-fixture-instance",
                status: .active
            )
        case .invalid:
            return .revokedOrExpiredRemote(reason: .invalid)
        case .revoked:
            return .revokedOrExpiredRemote(reason: .revoked)
        }
    }

    private func validationFreshness(
        for verifyResult: VerifyResult,
        now: Date,
        lastSuccessfulVerifyAt: Date?
    ) -> ValidationFreshness {
        switch verifyResult {
        case .success:
            let lastValidAt = lastSuccessfulVerifyAt ?? now
            return .valid(
                lastValidAt: lastValidAt,
                nextCheckAt: lastValidAt.addingTimeInterval(24 * 60 * 60)
            )
        case .networkFailure, .serverError, .timeout:
            return EntitlementPolicy.offlineGrace(lastAttemptAt: now)
        case .invalid:
            return .hardFailure(reason: .invalid)
        case .revoked:
            return .hardFailure(reason: .revoked)
        }
    }

    private func stateLabel(_ entitlement: EffectiveEntitlement) -> String {
        switch entitlement {
        case let .proAllowed(reason):
            return reason.rawValue
        case let .proDenied(reason):
            return reason.rawValue
        }
    }

    private func decisionLabel(_ entitlement: EffectiveEntitlement) -> String {
        entitlement.isProAllowed ? "allowed" : "blocked"
    }

    private func decisionReasonLabel(_ entitlement: EffectiveEntitlement) -> String {
        switch entitlement {
        case let .proAllowed(reason):
            return reason.rawValue
        case let .proDenied(reason):
            return reason.rawValue
        }
    }

    private func trialStartedAtString(from state: TrialState) -> String? {
        switch state {
        case let .active(startedAt, _), let .expired(startedAt, _):
            return Self.string(from: startedAt)
        case .notInitialized, .invalidLocalRecord:
            return nil
        }
    }

    private func trialEndsAtString(from state: TrialState) -> String? {
        switch state {
        case let .active(_, expiresAt), let .expired(_, expiresAt):
            return Self.string(from: expiresAt)
        case .notInitialized, .invalidLocalRecord:
            return nil
        }
    }

    private func trialDaysRemaining(from state: TrialState, now: Date) -> Int? {
        guard case let .active(_, expiresAt) = state else { return nil }
        return max(0, Int(ceil(expiresAt.timeIntervalSince(now) / (24 * 60 * 60))))
    }

    private func lastVerifyResultLabel(from freshness: ValidationFreshness) -> String {
        switch freshness {
        case .neverChecked:
            return "neverChecked"
        case .valid:
            return "success"
        case .temporaryFailure:
            return "temporaryFailure"
        case let .hardFailure(reason):
            return reason.rawValue
        }
    }

    private func lastSuccessfulVerifyAtString(from freshness: ValidationFreshness) -> String? {
        switch freshness {
        case let .valid(lastValidAt, _):
            return Self.string(from: lastValidAt)
        case .neverChecked, .temporaryFailure, .hardFailure:
            return nil
        }
    }

    private func offlineGraceEndsAtString(from freshness: ValidationFreshness) -> String? {
        switch freshness {
        case let .temporaryFailure(_, graceUntil):
            return Self.string(from: graceUntil)
        case .neverChecked, .valid, .hardFailure:
            return nil
        }
    }

    private func offlineGraceRemainingDays(from freshness: ValidationFreshness, now: Date) -> Int? {
        guard case let .temporaryFailure(_, graceUntil) = freshness else { return nil }
        return max(0, Int(ceil(graceUntil.timeIntervalSince(now) / (24 * 60 * 60))))
    }

    private func licenseKeyPresent(in state: LicenseState) -> Bool {
        switch state {
        case .activated:
            return true
        case .none, .deactivatedLocal, .revokedOrExpiredRemote, .mismatch:
            return false
        }
    }

    private func licenseFingerprint(from state: LicenseState) -> String? {
        switch state {
        case let .activated(fingerprint, _, _):
            return fingerprint
        case .none, .deactivatedLocal, .revokedOrExpiredRemote, .mismatch:
            return nil
        }
    }

    private static func dateValue(for key: String, in arguments: [String]) -> Date? {
        guard let value = stringValue(for: key, in: arguments) else { return nil }

        return iso8601Date(from: value, fractionalSeconds: true)
            ?? iso8601Date(from: value, fractionalSeconds: false)
    }

    private static func stringValue(for key: String, in arguments: [String]) -> String? {
        let prefix = "\(key)="
        guard let argument = arguments.first(where: { $0.hasPrefix(prefix) }) else {
            return nil
        }

        return String(argument.dropFirst(prefix.count))
    }

    private static func string(from date: Date) -> String {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return formatter.string(from: date)
    }

    private static func iso8601Date(from value: String, fractionalSeconds: Bool) -> Date? {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = fractionalSeconds
            ? [.withInternetDateTime, .withFractionalSeconds]
            : [.withInternetDateTime]
        return formatter.date(from: value)
    }
}
