import Foundation

// MARK: - Cached Plan Info (Codable)

public struct WindsurfCachedPlanInfo: Codable, Sendable {
    public let planName: String?
    public let startTimestamp: Int64?
    public let endTimestamp: Int64?
    public let usage: Usage?
    public let quotaUsage: QuotaUsage?

    public struct Usage: Codable, Sendable {
        public let messages: Int?
        public let usedMessages: Int?
        public let remainingMessages: Int?
        public let flowActions: Int?
        public let usedFlowActions: Int?
        public let remainingFlowActions: Int?
        public let flexCredits: Int?
        public let usedFlexCredits: Int?
        public let remainingFlexCredits: Int?
    }

    public struct QuotaUsage: Codable, Sendable {
        public let dailyRemainingPercent: Double?
        public let weeklyRemainingPercent: Double?
        public let dailyResetAtUnix: Int64?
        public let weeklyResetAtUnix: Int64?
    }
}

// MARK: - Errors & Probe

#if os(macOS)

import SQLite3

public enum WindsurfStatusProbeError: LocalizedError, Sendable, Equatable {
    case dbNotFound(String)
    case sqliteFailed(String)
    case noData
    case parseFailed(String)

    public var errorDescription: String? {
        switch self {
        case let .dbNotFound(path):
            "Windsurf database not found at \(path). Ensure Windsurf is installed and has been launched at least once."
        case let .sqliteFailed(message):
            "SQLite error reading Windsurf data: \(message)"
        case .noData:
            "No plan data found in Windsurf database. Sign in to Windsurf first."
        case let .parseFailed(message):
            "Could not parse Windsurf plan data: \(message)"
        }
    }
}

// MARK: - Probe

public struct WindsurfStatusProbe: Sendable {
    private static let defaultDBPath: String = {
        let home = NSHomeDirectory()
        return "\(home)/Library/Application Support/Windsurf/User/globalStorage/state.vscdb"
    }()

    private static let query = "SELECT value FROM ItemTable WHERE key = 'windsurf.settings.cachedPlanInfo' LIMIT 1;"

    private let dbPath: String

    public init(dbPath: String? = nil) {
        self.dbPath = dbPath ?? Self.defaultDBPath
    }

    public func fetch() throws -> WindsurfCachedPlanInfo {
        guard FileManager.default.fileExists(atPath: self.dbPath) else {
            throw WindsurfStatusProbeError.dbNotFound(self.dbPath)
        }

        var db: OpaquePointer?
        guard sqlite3_open_v2(self.dbPath, &db, SQLITE_OPEN_READONLY, nil) == SQLITE_OK else {
            let message = db.flatMap { String(cString: sqlite3_errmsg($0)) } ?? "unknown error"
            sqlite3_close(db)
            throw WindsurfStatusProbeError.sqliteFailed(message)
        }
        defer { sqlite3_close(db) }

        sqlite3_busy_timeout(db, 250)

        var stmt: OpaquePointer?
        guard sqlite3_prepare_v2(db, Self.query, -1, &stmt, nil) == SQLITE_OK else {
            let message = db.flatMap { String(cString: sqlite3_errmsg($0)) } ?? "unknown error"
            throw WindsurfStatusProbeError.sqliteFailed(message)
        }
        defer { sqlite3_finalize(stmt) }

        let stepResult = sqlite3_step(stmt)
        guard stepResult == SQLITE_ROW else {
            if stepResult == SQLITE_DONE {
                throw WindsurfStatusProbeError.noData
            }
            let message = db.flatMap { String(cString: sqlite3_errmsg($0)) } ?? "unknown error"
            throw WindsurfStatusProbeError.sqliteFailed(message)
        }

        guard let jsonString = Self.decodeSQLiteValue(stmt: stmt, index: 0) else {
            throw WindsurfStatusProbeError.noData
        }
        guard let jsonData = jsonString.data(using: .utf8) else {
            throw WindsurfStatusProbeError.parseFailed("Invalid UTF-8 encoding")
        }

        do {
            return try JSONDecoder().decode(WindsurfCachedPlanInfo.self, from: jsonData)
        } catch {
            throw WindsurfStatusProbeError.parseFailed(error.localizedDescription)
        }
    }

    private static func decodeSQLiteValue(stmt: OpaquePointer?, index: Int32) -> String? {
        switch sqlite3_column_type(stmt, index) {
        case SQLITE_TEXT:
            guard let c = sqlite3_column_text(stmt, index) else { return nil }
            return String(cString: c)
        case SQLITE_BLOB:
            guard let bytes = sqlite3_column_blob(stmt, index) else { return nil }
            let data = Data(bytes: bytes, count: Int(sqlite3_column_bytes(stmt, index)))
            // VSCode/Windsurf state.vscdb schema declares value as BLOB;
            // only accept decodes that still parse as JSON to avoid UTF-16 mojibake.
            return self.decodeJSONBlob(data)
        default:
            return nil
        }
    }

    private static func decodeJSONBlob(_ data: Data) -> String? {
        for encoding in [String.Encoding.utf8, .utf16LittleEndian] {
            guard let decoded = String(data: data, encoding: encoding) else { continue }
            let trimmed = decoded.trimmingCharacters(in: .controlCharacters)
            guard let jsonData = trimmed.data(using: .utf8),
                  (try? JSONSerialization.jsonObject(with: jsonData)) != nil
            else {
                continue
            }
            return trimmed
        }
        return nil
    }
}

#else

// MARK: - Windsurf (Unsupported)

public enum WindsurfStatusProbeError: LocalizedError, Sendable, Equatable {
    case notSupported

    public var errorDescription: String? {
        "Windsurf is only supported on macOS."
    }
}

public struct WindsurfStatusProbe: Sendable {
    public init(dbPath _: String? = nil) {}

    public func fetch() throws -> WindsurfCachedPlanInfo {
        throw WindsurfStatusProbeError.notSupported
    }
}

#endif

// MARK: - Conversion to UsageSnapshot

extension WindsurfCachedPlanInfo {
    public func toUsageSnapshot() -> UsageSnapshot {
        var primary: RateWindow?
        var secondary: RateWindow?

        if let quota = self.quotaUsage {
            // Primary: daily usage (usedPercent = 100 - dailyRemainingPercent)
            if let daily = quota.dailyRemainingPercent {
                let resetDate = quota.dailyResetAtUnix.map {
                    Date(timeIntervalSince1970: TimeInterval($0))
                }
                primary = RateWindow(
                    usedPercent: max(0, min(100, 100 - daily)),
                    windowMinutes: nil,
                    resetsAt: resetDate,
                    resetDescription: Self.formatResetDescription(resetDate))
            }

            // Secondary: weekly usage
            if let weekly = quota.weeklyRemainingPercent {
                let resetDate = quota.weeklyResetAtUnix.map {
                    Date(timeIntervalSince1970: TimeInterval($0))
                }
                secondary = RateWindow(
                    usedPercent: max(0, min(100, 100 - weekly)),
                    windowMinutes: nil,
                    resetsAt: resetDate,
                    resetDescription: Self.formatResetDescription(resetDate))
            }
        }

        if primary == nil, let usage = self.usage {
            primary = Self.makeUsageWindow(
                used: usage.usedMessages,
                remaining: usage.remainingMessages,
                total: usage.messages,
                unit: "messages")
        }

        if secondary == nil, let usage = self.usage {
            secondary = Self.makeUsageWindow(
                used: usage.usedFlowActions,
                remaining: usage.remainingFlowActions,
                total: usage.flowActions,
                unit: "flow actions")
        }

        // Identity
        var orgDescription: String?
        if let endTimestamp = self.endTimestamp {
            let endDate = Date(timeIntervalSince1970: TimeInterval(endTimestamp) / 1000)
            let formatter = DateFormatter()
            formatter.dateStyle = .medium
            formatter.timeStyle = .none
            orgDescription = "Expires \(formatter.string(from: endDate))"
        }

        let identity = ProviderIdentitySnapshot(
            providerID: .windsurf,
            accountEmail: nil,
            accountOrganization: orgDescription,
            loginMethod: self.planName)

        return UsageSnapshot(
            primary: primary,
            secondary: secondary,
            updatedAt: Date(),
            identity: identity)
    }

    private static func makeUsageWindow(
        used rawUsed: Int?,
        remaining rawRemaining: Int?,
        total rawTotal: Int?,
        unit: String) -> RateWindow?
    {
        guard let total = rawTotal, total > 0 else { return nil }
        let inferredUsed = rawUsed ?? rawRemaining.map { max(0, total - $0) }
        guard let used = inferredUsed else { return nil }
        let clampedUsed = max(0, min(total, used))
        let usedPercent = max(0, min(100, Double(clampedUsed) / Double(total) * 100))
        return RateWindow(
            usedPercent: usedPercent,
            windowMinutes: nil,
            resetsAt: nil,
            resetDescription: "\(clampedUsed) / \(total) \(unit)")
    }

    static func formatResetDescription(_ date: Date?, now: Date = Date()) -> String? {
        guard let date else { return nil }
        let interval = date.timeIntervalSince(now)
        guard interval > 0 else { return "Expired" }

        let hours = Int(interval / 3600)
        let minutes = Int((interval.truncatingRemainder(dividingBy: 3600)) / 60)

        if hours >= 24 {
            let days = hours / 24
            let remainingHours = hours % 24
            return "Resets in \(days)d \(remainingHours)h"
        } else if hours > 0 {
            return "Resets in \(hours)h \(minutes)m"
        } else {
            return "Resets in \(minutes)m"
        }
    }
}
