import Foundation
import Combine
import QuartzCore
import os

/// Lightweight performance profiler for identifying CPU hotspots.
///
/// Tracks call frequency and duration of critical paths.
/// Output: Xcode console via os.Logger (subsystem: com.rosetta.messenger).
///
/// Usage:
///   PerformanceLogger.shared.track("dialogPersist")
///   PerformanceLogger.shared.measure("imageDownsample") { ... }
///
/// Periodic report (every 10s) shows calls/sec for all tracked events.
/// Enable with `PerformanceLogger.shared.isEnabled = true`.
@MainActor
final class PerformanceLogger {
    static let shared = PerformanceLogger()

    private static let logger = Logger(subsystem: "com.rosetta.messenger", category: "Perf")

    /// Enable for performance testing. Disabled by default — zero overhead in production.
    #if DEBUG
    var isEnabled = true // Set to `true` to enable perf logging
    #else
    var isEnabled = false
    #endif

    // MARK: - Event Counters

    private var counters: [String: Int] = [:]
    private var durations: [String: [CFTimeInterval]] = [:]
    private var reportTask: Task<Void, Never>?
    private var startTime = CACurrentMediaTime()

    private init() {
        Self.logger.info("⚡ PerformanceLogger initialized (isEnabled=\(self.isEnabled))")
        startPeriodicReport()
    }

    /// Track a single occurrence of an event (e.g., body evaluation, persist call).
    /// Returns `true` so it can be used in SwiftUI body: `let _ = PerformanceLogger.shared.track("x")`
    @discardableResult
    func track(_ event: String) -> Bool {
        guard isEnabled else { return true }
        counters[event, default: 0] += 1
        return true
    }

    // MARK: - Frame Drop Detection

    private var lastFrameTime: CFTimeInterval = 0
    private var frameDrops: Int = 0
    private var worstFrameMs: Double = 0

    /// Call at start of expensive body eval. Returns token to pass to `endBodyEval`.
    func beginBodyEval() -> CFTimeInterval {
        CACurrentMediaTime()
    }

    /// Call at end of body eval. Logs if it took >8ms (frame budget at 120Hz).
    func endBodyEval(_ start: CFTimeInterval, label: String) {
        guard isEnabled else { return }
        let elapsed = (CACurrentMediaTime() - start) * 1000 // ms
        if elapsed > 8 { // >8ms = dropped frame at 120Hz
            frameDrops += 1
            if elapsed > worstFrameMs { worstFrameMs = elapsed }
            print("🔴 SLOW \(label): \(String(format: "%.1f", elapsed))ms")
        }
    }

    /// Measure duration of a synchronous block.
    func measure<T>(_ event: String, block: () -> T) -> T {
        guard isEnabled else { return block() }
        let start = CACurrentMediaTime()
        let result = block()
        let elapsed = CACurrentMediaTime() - start
        counters[event, default: 0] += 1
        durations[event, default: []].append(elapsed)
        // Keep last 50 measurements
        if durations[event]!.count > 50 {
            durations[event] = Array(durations[event]!.suffix(50))
        }
        return result
    }

    /// Measure duration of an async block.
    func measureAsync<T>(_ event: String, block: () async throws -> T) async rethrows -> T {
        guard isEnabled else { return try await block() }
        let start = CACurrentMediaTime()
        let result = try await block()
        let elapsed = CACurrentMediaTime() - start
        counters[event, default: 0] += 1
        durations[event, default: []].append(elapsed)
        if durations[event]!.count > 50 {
            durations[event] = Array(durations[event]!.suffix(50))
        }
        return result
    }

    // MARK: - FPS Tracking (real-time)

    private var fpsDisplayLink: CADisplayLink?
    private var fpsFrameCount: Int = 0
    private var fpsLastTimestamp: CFTimeInterval = 0
    /// Current FPS — updated every 0.5s. Read from FPSOverlay.
    private(set) var currentFPS: Int = 0
    /// Minimum FPS in last measurement window.
    private(set) var minFPS: Int = 60
    private var fpsHistory: [Int] = []

    /// Start real-time FPS tracking via CADisplayLink.
    func startFPSTracking() {
        guard fpsDisplayLink == nil else { return }
        let link = CADisplayLink(target: FPSDisplayLinkTarget { [weak self] link in
            self?.fpsLinkTick(link)
        }, selector: #selector(FPSDisplayLinkTarget.tick))
        link.add(to: .main, forMode: .common)
        fpsDisplayLink = link
        fpsLastTimestamp = CACurrentMediaTime()
    }

    func stopFPSTracking() {
        fpsDisplayLink?.invalidate()
        fpsDisplayLink = nil
    }

    private func fpsLinkTick(_ link: CADisplayLink) {
        fpsFrameCount += 1
        let now = CACurrentMediaTime()
        let elapsed = now - fpsLastTimestamp
        if elapsed >= 0.5 {
            let fps = Int(Double(fpsFrameCount) / elapsed)
            currentFPS = fps
            fpsFrameCount = 0
            fpsLastTimestamp = now
            fpsHistory.append(fps)
            if fpsHistory.count > 10 { fpsHistory.removeFirst() }
            minFPS = fpsHistory.min() ?? fps
        }
    }

    // MARK: - Periodic Report

    private func startPeriodicReport() {
        reportTask = Task { [weak self] in
            while !Task.isCancelled {
                try? await Task.sleep(for: .seconds(10))
                guard let self, self.isEnabled else { continue }
                self.printReport()
            }
        }
    }

    private func printReport() {
        let elapsed = CACurrentMediaTime() - startTime
        guard elapsed > 1, !counters.isEmpty else { return }

        var lines: [String] = ["⚡ PERF (last \(Int(elapsed))s) FPS:\(currentFPS) min:\(minFPS) drops:\(frameDrops) worst:\(String(format: "%.0f", worstFrameMs))ms:"]
        frameDrops = 0
        worstFrameMs = 0

        let sortedEvents = counters.sorted { $0.value > $1.value }
        for (event, count) in sortedEvents {
            let perSec = Double(count) / elapsed
            var line = "  \(event): \(count)× (\(String(format: "%.1f", perSec))/s)"

            if let durs = durations[event], !durs.isEmpty {
                let avg = durs.reduce(0, +) / Double(durs.count)
                let max = durs.max() ?? 0
                line += " avg=\(String(format: "%.1f", avg * 1000))ms max=\(String(format: "%.1f", max * 1000))ms"
            }

            lines.append(line)
        }

        let report = lines.joined(separator: "\n")
        #if DEBUG
        print(report)
        #endif
        Self.logger.info("\(report)")

        // Reset counters for next period
        counters.removeAll(keepingCapacity: true)
        durations.removeAll(keepingCapacity: true)
        startTime = CACurrentMediaTime()
    }
}

// MARK: - CADisplayLink target (avoids @objc on @MainActor)

private class FPSDisplayLinkTarget {
    let handler: (CADisplayLink) -> Void
    init(handler: @escaping (CADisplayLink) -> Void) { self.handler = handler }
    @objc func tick(_ link: CADisplayLink) { handler(link) }
}

// MARK: - FPS Overlay View

import SwiftUI

/// Floating FPS counter — add to any view with `.overlay { FPSOverlayView() }`.
/// Shows current FPS (green/yellow/red) and min FPS over last 5s.
/// Drag to reposition. Double-tap to dismiss.
struct FPSOverlayView: View {
    @State private var fps: Int = 0
    @State private var minFPS: Int = 60
    @State private var offset: CGSize = .zero
    @State private var isVisible = true
    private let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()

    var body: some View {
        if isVisible {
            VStack(alignment: .leading, spacing: 2) {
                Text("\(fps) FPS")
                    .font(.system(size: 13, weight: .bold, design: .monospaced))
                    .foregroundStyle(fpsColor)
                Text("min \(minFPS)")
                    .font(.system(size: 10, weight: .medium, design: .monospaced))
                    .foregroundStyle(minFPSColor)
            }
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
            .background(Color.black.opacity(0.75))
            .clipShape(RoundedRectangle(cornerRadius: 8))
            .offset(offset)
            .gesture(
                DragGesture()
                    .onChanged { offset = $0.translation }
            )
            .onTapGesture(count: 2) { isVisible = false }
            .onReceive(timer) { _ in
                fps = PerformanceLogger.shared.currentFPS
                minFPS = PerformanceLogger.shared.minFPS
            }
            .onAppear { PerformanceLogger.shared.startFPSTracking() }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
            .padding(.leading, 16)
            .padding(.top, 55)
            .allowsHitTesting(true)
        }
    }

    private var fpsColor: Color {
        if fps >= 55 { return .green }
        if fps >= 30 { return .yellow }
        return .red
    }

    private var minFPSColor: Color {
        if minFPS >= 45 { return .green.opacity(0.7) }
        if minFPS >= 25 { return .yellow.opacity(0.7) }
        return .red.opacity(0.7)
    }
}
