import Foundation
import SwiftData

@Model final class MetricTimeoutProfile {
    static let defaultInitialTimeout: TimeInterval = 15
    static let maximumTimeout: TimeInterval = 120
    private static let sampleLimit = 30
    private static let p95MinimumSampleCount = 5

    var id: UUID = UUID()
    var metricIdentifier: String = ""
    var displayName: String = ""
    var lastSuccessfulElapsed: TimeInterval = 0
    var averageSuccessfulElapsed: TimeInterval = 0
    var p95SuccessfulElapsed: TimeInterval = 0
    var lastTimeoutElapsed: TimeInterval = 0
    var timeoutCount: Int = 0
    var successCount: Int = 0
    var lastUpdatedAt: Date = Date.distantPast
    var currentTimeout: TimeInterval = MetricTimeoutProfile.defaultInitialTimeout
    var isManuallyOverridden: Bool = false
    var manualTimeoutValue: TimeInterval = MetricTimeoutProfile.defaultInitialTimeout
    var successfulElapsedSamplesJSON: String = "[]"

    init(metricIdentifier: String, displayName: String) {
        self.id = UUID()
        self.metricIdentifier = metricIdentifier
        self.displayName = displayName
        self.currentTimeout = Self.defaultInitialTimeout
        self.manualTimeoutValue = Self.defaultInitialTimeout
    }
}

extension MetricTimeoutProfile {
    var effectiveTimeout: TimeInterval {
        clamp(isManuallyOverridden ? manualTimeoutValue : currentTimeout)
    }

    var timeoutMode: String {
        if isManuallyOverridden { return "manual" }
        return successCount == 0 && timeoutCount == 0 ? "default" : "adaptive"
    }

    var hasP95: Bool {
        successSamples.count >= Self.p95MinimumSampleCount
    }

    var p95SuccessfulElapsedIfAvailable: TimeInterval? {
        hasP95 ? p95SuccessfulElapsed : nil
    }

    var suggestedRetryTimeout: TimeInterval {
        clamp(max(effectiveTimeout * 2, lastSuccessfulElapsed * 2, Self.defaultInitialTimeout))
    }

    func recordSuccess(elapsed: TimeInterval, displayName: String) {
        self.displayName = displayName
        lastSuccessfulElapsed = elapsed
        successCount += 1
        lastUpdatedAt = Date()

        var samples = successSamples
        samples.append(elapsed)
        if samples.count > Self.sampleLimit {
            samples.removeFirst(samples.count - Self.sampleLimit)
        }
        successSamples = samples

        averageSuccessfulElapsed = samples.reduce(0, +) / Double(samples.count)
        p95SuccessfulElapsed = Self.percentile95(samples)
        currentTimeout = automaticTimeout(from: samples)
    }

    func recordTimeout(elapsed: TimeInterval, displayName: String) {
        self.displayName = displayName
        lastTimeoutElapsed = elapsed
        timeoutCount += 1
        lastUpdatedAt = Date()

        guard !isManuallyOverridden else { return }
        let progressive = max(currentTimeout * 1.5, elapsed * 2, Self.defaultInitialTimeout)
        currentTimeout = clamp(progressive)
    }

    func resetLearning(displayName: String? = nil) {
        if let displayName { self.displayName = displayName }
        lastSuccessfulElapsed = 0
        averageSuccessfulElapsed = 0
        p95SuccessfulElapsed = 0
        lastTimeoutElapsed = 0
        timeoutCount = 0
        successCount = 0
        lastUpdatedAt = Date()
        currentTimeout = Self.defaultInitialTimeout
        isManuallyOverridden = false
        manualTimeoutValue = Self.defaultInitialTimeout
        successfulElapsedSamplesJSON = "[]"
    }

    func setManualTimeout(_ value: TimeInterval) {
        manualTimeoutValue = clamp(value)
        isManuallyOverridden = true
        lastUpdatedAt = Date()
    }

    func returnToAutomatic() {
        isManuallyOverridden = false
        lastUpdatedAt = Date()
    }

    private var successSamples: [TimeInterval] {
        get {
            guard let data = successfulElapsedSamplesJSON.data(using: .utf8),
                  let values = try? JSONDecoder().decode([TimeInterval].self, from: data) else {
                return []
            }
            return values
        }
        set {
            successfulElapsedSamplesJSON = (try? String(data: JSONEncoder().encode(newValue), encoding: .utf8)) ?? "[]"
        }
    }

    private func automaticTimeout(from samples: [TimeInterval]) -> TimeInterval {
        let basis = samples.count >= Self.p95MinimumSampleCount ? Self.percentile95(samples) : samples.max() ?? 0
        return clamp(max(Self.defaultInitialTimeout, basis * 2, currentTimeout * 0.9))
    }

    private func clamp(_ value: TimeInterval) -> TimeInterval {
        min(max(value, Self.defaultInitialTimeout), Self.maximumTimeout)
    }

    private static func percentile95(_ samples: [TimeInterval]) -> TimeInterval {
        guard !samples.isEmpty else { return 0 }
        let sorted = samples.sorted()
        let rawIndex = Int(ceil(Double(sorted.count) * 0.95)) - 1
        let index = min(max(rawIndex, 0), sorted.count - 1)
        return sorted[index]
    }
}
