import Foundation

public enum ProFeature: String, CaseIterable, Codable, Equatable, Sendable {
    case windowShortcutActivation
}

public enum TrialInvalidReason: String, Codable, Equatable, Sendable {
    case corruptedRecord
    case expiresBeforeStart
    case startedInFuture
    case timeRollback
}

public enum TrialState: Codable, Equatable, Sendable {
    case notInitialized
    case active(startedAt: Date, expiresAt: Date)
    case expired(startedAt: Date, expiredAt: Date)
    case invalidLocalRecord(reason: TrialInvalidReason)
}

public enum LicenseActivationStatus: String, Codable, Equatable, Sendable {
    case active
    case disabled
    case expired
}

public enum LicenseRemoteFailureReason: String, Codable, Equatable, Sendable {
    case invalid
    case revoked
    case disabled
    case expired
    case unknown
}

public enum LicenseMismatchReason: String, Codable, Equatable, Sendable {
    case store
    case product
    case variant
}

public enum LicenseState: Codable, Equatable, Sendable {
    case none
    case activated(fingerprint: String, instanceID: String, status: LicenseActivationStatus)
    case deactivatedLocal
    case revokedOrExpiredRemote(reason: LicenseRemoteFailureReason)
    case mismatch(reason: LicenseMismatchReason)
}

public enum ValidationHardFailureReason: String, Codable, Equatable, Sendable {
    case invalid
    case revoked
    case disabled
    case expired
    case productMismatch
    case storeMismatch
    case variantMismatch
    case serverRejected
    case unknown
}

public enum ValidationFreshness: Codable, Equatable, Sendable {
    case neverChecked
    case valid(lastValidAt: Date, nextCheckAt: Date)
    case temporaryFailure(lastAttemptAt: Date, graceUntil: Date)
    case hardFailure(reason: ValidationHardFailureReason)
}

public enum EntitlementAllowedReason: String, Codable, Equatable, Sendable {
    case trialActive
    case licenseValid
    case offlineGrace
}

public enum EntitlementDeniedReason: String, Codable, Equatable, Sendable {
    case trialNotInitialized
    case trialExpired
    case noLicense
    case licenseDeactivated
    case licenseRevokedOrExpired
    case licenseMismatch
    case licenseDisabled
    case licenseExpired
    case verificationRequired
    case graceExpired
    case hardFailure
    case invalidLocalRecord
    case unsupportedFeature
}

public enum EffectiveEntitlement: Codable, Equatable, Sendable {
    case proAllowed(reason: EntitlementAllowedReason)
    case proDenied(reason: EntitlementDeniedReason)

    public var isProAllowed: Bool {
        switch self {
        case .proAllowed:
            return true
        case .proDenied:
            return false
        }
    }
}

public struct EntitlementSnapshot: Codable, Equatable, Sendable {
    public var trialState: TrialState
    public var licenseState: LicenseState
    public var validationFreshness: ValidationFreshness

    public init(
        trialState: TrialState,
        licenseState: LicenseState,
        validationFreshness: ValidationFreshness
    ) {
        self.trialState = trialState
        self.licenseState = licenseState
        self.validationFreshness = validationFreshness
    }
}

public protocol ClockProtocol: Sendable {
    var now: Date { get }
}

public struct SystemClock: ClockProtocol {
    public init() {}

    public var now: Date {
        Date()
    }
}

public protocol LicenseStoreProtocol: Sendable {
    func loadEntitlementSnapshot() throws -> EntitlementSnapshot
    func saveEntitlementSnapshot(_ snapshot: EntitlementSnapshot) throws
}

public enum LicenseValidationResult: Equatable, Sendable {
    case success(lastValidAt: Date, nextCheckAt: Date)
    case temporaryFailure(lastAttemptAt: Date, graceUntil: Date)
    case hardFailure(reason: ValidationHardFailureReason)
}

public protocol LicenseValidationClientProtocol: Sendable {
    func validateLicense(fingerprint: String, instanceID: String) async throws -> LicenseValidationResult
}

public protocol EntitlementProviderProtocol: Sendable {
    func entitlement(for feature: ProFeature, at now: Date) -> EffectiveEntitlement
}

public enum EntitlementPolicy {
    public static let defaultTrialDuration: TimeInterval = 30 * 24 * 60 * 60
    public static let defaultOfflineGraceDuration: TimeInterval = 14 * 24 * 60 * 60

    public static func trialStarted(at startedAt: Date, duration: TimeInterval = defaultTrialDuration) -> TrialState {
        .active(startedAt: startedAt, expiresAt: startedAt.addingTimeInterval(duration))
    }

    public static func offlineGrace(
        lastAttemptAt: Date,
        duration: TimeInterval = defaultOfflineGraceDuration
    ) -> ValidationFreshness {
        .temporaryFailure(lastAttemptAt: lastAttemptAt, graceUntil: lastAttemptAt.addingTimeInterval(duration))
    }

    public static func evaluate(
        snapshot: EntitlementSnapshot,
        feature: ProFeature,
        now: Date
    ) -> EffectiveEntitlement {
        switch feature {
        case .windowShortcutActivation:
            break
        }

        let licenseDecision = decisionForLicense(
            state: snapshot.licenseState,
            freshness: snapshot.validationFreshness,
            now: now
        )
        if case .proAllowed = licenseDecision {
            return licenseDecision
        }

        let trialDecision = decisionForTrial(state: snapshot.trialState, now: now)
        if case .proAllowed = trialDecision {
            return trialDecision
        }

        if case .none = snapshot.licenseState {
            if case .notInitialized = snapshot.trialState {
                return .proDenied(reason: .noLicense)
            }
            return trialDecision
        }

        return licenseDecision
    }

    private static func decisionForTrial(state: TrialState, now: Date) -> EffectiveEntitlement {
        switch state {
        case .notInitialized:
            return .proDenied(reason: .trialNotInitialized)
        case let .active(startedAt, expiresAt):
            guard expiresAt > startedAt else {
                return .proDenied(reason: .invalidLocalRecord)
            }
            guard now >= startedAt else {
                return .proDenied(reason: .invalidLocalRecord)
            }
            guard now < expiresAt else {
                return .proDenied(reason: .trialExpired)
            }
            return .proAllowed(reason: .trialActive)
        case .expired:
            return .proDenied(reason: .trialExpired)
        case .invalidLocalRecord:
            return .proDenied(reason: .invalidLocalRecord)
        }
    }

    private static func decisionForLicense(
        state: LicenseState,
        freshness: ValidationFreshness,
        now: Date
    ) -> EffectiveEntitlement {
        switch state {
        case .none:
            return .proDenied(reason: .noLicense)
        case .deactivatedLocal:
            return .proDenied(reason: .licenseDeactivated)
        case .revokedOrExpiredRemote:
            return .proDenied(reason: .licenseRevokedOrExpired)
        case .mismatch:
            return .proDenied(reason: .licenseMismatch)
        case let .activated(_, _, status):
            switch status {
            case .active:
                return decisionForValidationFreshness(freshness, now: now)
            case .disabled:
                return .proDenied(reason: .licenseDisabled)
            case .expired:
                return .proDenied(reason: .licenseExpired)
            }
        }
    }

    private static func decisionForValidationFreshness(
        _ freshness: ValidationFreshness,
        now: Date
    ) -> EffectiveEntitlement {
        switch freshness {
        case .neverChecked:
            return .proDenied(reason: .verificationRequired)
        case let .valid(_, nextCheckAt):
            return now <= nextCheckAt
                ? .proAllowed(reason: .licenseValid)
                : .proDenied(reason: .verificationRequired)
        case let .temporaryFailure(_, graceUntil):
            return now < graceUntil
                ? .proAllowed(reason: .offlineGrace)
                : .proDenied(reason: .graceExpired)
        case .hardFailure:
            return .proDenied(reason: .hardFailure)
        }
    }
}

public enum EntitlementFixtureState: String, CaseIterable, Codable, Equatable, Sendable {
    case trialActive
    case trialExpired
    case proUnlocked
    case offlineGrace
    case verifyFailed
}

public struct FixtureEntitlementProvider: EntitlementProviderProtocol {
    public let snapshot: EntitlementSnapshot

    public init(fixtureState: EntitlementFixtureState, now: Date) {
        let startedAt = now.addingTimeInterval(-24 * 60 * 60)
        let expiredAt = now.addingTimeInterval(-24 * 60 * 60)
        let expiredTrial = TrialState.expired(
            startedAt: now.addingTimeInterval(-31 * 24 * 60 * 60),
            expiredAt: expiredAt
        )
        let activeLicense = LicenseState.activated(
            fingerprint: "fixture-fingerprint",
            instanceID: "fixture-instance",
            status: .active
        )

        switch fixtureState {
        case .trialActive:
            snapshot = EntitlementSnapshot(
                trialState: .active(
                    startedAt: startedAt,
                    expiresAt: now.addingTimeInterval(29 * 24 * 60 * 60)
                ),
                licenseState: .none,
                validationFreshness: .neverChecked
            )
        case .trialExpired:
            snapshot = EntitlementSnapshot(
                trialState: expiredTrial,
                licenseState: .none,
                validationFreshness: .neverChecked
            )
        case .proUnlocked:
            snapshot = EntitlementSnapshot(
                trialState: expiredTrial,
                licenseState: activeLicense,
                validationFreshness: .valid(
                    lastValidAt: now,
                    nextCheckAt: now.addingTimeInterval(24 * 60 * 60)
                )
            )
        case .offlineGrace:
            snapshot = EntitlementSnapshot(
                trialState: expiredTrial,
                licenseState: activeLicense,
                validationFreshness: EntitlementPolicy.offlineGrace(lastAttemptAt: now)
            )
        case .verifyFailed:
            snapshot = EntitlementSnapshot(
                trialState: expiredTrial,
                licenseState: activeLicense,
                validationFreshness: .hardFailure(reason: .serverRejected)
            )
        }
    }

    public func entitlement(for feature: ProFeature, at now: Date) -> EffectiveEntitlement {
        EntitlementPolicy.evaluate(snapshot: snapshot, feature: feature, now: now)
    }
}
