import Foundation
import AppKit
import CoreGraphics
import ApplicationServices

enum SkrybeError: LocalizedError {
    case invalidUsage(String)
    case unsupportedSubcommand(String)
    case unsupportedQuotesOption(String)
    case apiURLInvalid
    case apiUnreachable(String)
    case invalidHTTPStatus(Int)
    case invalidResponse
    case pasteboardWriteFailed
    case accessibilityPermissionMissing
    case eventCreationFailed

    var errorDescription: String? {
        switch self {
        case .invalidUsage(let message):
            return message
        case .unsupportedSubcommand(let command):
            return "Unsupported subcommand: \(command)"
        case .unsupportedQuotesOption(let option):
            return "Unsupported quotes option: \(option)"
        case .apiURLInvalid:
            return "Invalid quotes API URL."
        case .apiUnreachable(let reason):
            return "Unable to reach quotes API: \(reason)"
        case .invalidHTTPStatus(let status):
            return "Quotes API returned HTTP status \(status)."
        case .invalidResponse:
            return "Quotes API returned an invalid response format."
        case .pasteboardWriteFailed:
            return "Failed to write quote text to the macOS pasteboard."
        case .accessibilityPermissionMissing:
            return "Accessibility permission is required to simulate paste (Cmd+V). Enable it in System Settings > Privacy & Security > Accessibility."
        case .eventCreationFailed:
            return "Unable to create keyboard events for paste operation."
        }
    }
}

struct QuoteResponse: Decodable {
    let quote: String
    let author: String
}

struct QuoteService {
    private let endpoint = "http://localhost:3001/quote"

    func fetchRandomQuote(timeout: TimeInterval = 5.0) throws -> QuoteResponse {
        guard let url = URL(string: endpoint) else {
            throw SkrybeError.apiURLInvalid
        }

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.timeoutInterval = timeout

        let semaphore = DispatchSemaphore(value: 0)
        var responseData: Data?
        var response: URLResponse?
        var requestError: Error?

        let task = URLSession.shared.dataTask(with: request) { data, urlResponse, error in
            responseData = data
            response = urlResponse
            requestError = error
            semaphore.signal()
        }

        task.resume()
        _ = semaphore.wait(timeout: .now() + timeout + 1.0)

        if let requestError {
            throw SkrybeError.apiUnreachable(requestError.localizedDescription)
        }

        guard let http = response as? HTTPURLResponse else {
            throw SkrybeError.invalidResponse
        }

        guard (200...299).contains(http.statusCode) else {
            throw SkrybeError.invalidHTTPStatus(http.statusCode)
        }

        guard let responseData else {
            throw SkrybeError.invalidResponse
        }

        do {
            let decoded = try JSONDecoder().decode(QuoteResponse.self, from: responseData)
            return decoded
        } catch {
            throw SkrybeError.invalidResponse
        }
    }
}

struct QuotePaster {
    func paste(_ text: String) throws {
        let pasteboard = NSPasteboard.general
        pasteboard.clearContents()

        guard pasteboard.setString(text, forType: .string) else {
            throw SkrybeError.pasteboardWriteFailed
        }

        guard AXIsProcessTrusted() else {
            throw SkrybeError.accessibilityPermissionMissing
        }

        try sendCommandV()
    }

    private func sendCommandV() throws {
        guard
            let source = CGEventSource(stateID: .hidSystemState),
            let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 9, keyDown: true), // 9 = 'v'
            let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 9, keyDown: false)
        else {
            throw SkrybeError.eventCreationFailed
        }

        keyDown.flags = .maskCommand
        keyUp.flags = .maskCommand

        keyDown.post(tap: .cghidEventTap)
        keyUp.post(tap: .cghidEventTap)
    }
}

struct SkrybeCLI {
    private let quoteService = QuoteService()
    private let quotePaster = QuotePaster()

    func run(arguments: [String]) throws {
        guard arguments.count >= 2 else {
            throw SkrybeError.invalidUsage(usage())
        }

        let command = arguments[1]
        switch command {
        case "quotes":
            try runQuotes(arguments: Array(arguments.dropFirst(2)))
        case "--help", "-h", "help":
            print(usage())
        default:
            throw SkrybeError.unsupportedSubcommand(command)
        }
    }

    private func runQuotes(arguments: [String]) throws {
        guard let option = arguments.first else {
            throw SkrybeError.invalidUsage(quotesUsage())
        }

        switch option {
        case "--paste":
            let quote = try quoteService.fetchRandomQuote()
            let rendered = "\(quote.quote) — \(quote.author)"
            try quotePaster.paste(rendered)
            print("✅ Pasted quote at cursor position")
        case "--list", "--add":
            throw SkrybeError.invalidUsage("\(option) is not implemented yet. Use `skrybe quotes --paste`.")
        case "--help", "-h", "help":
            print(quotesUsage())
        default:
            throw SkrybeError.unsupportedQuotesOption(option)
        }
    }

    private func usage() -> String {
        return """
        Usage:
          skrybe quotes --paste
          skrybe quotes --list   (planned)
          skrybe quotes --add    (planned)
        """
    }

    private func quotesUsage() -> String {
        return """
        Quotes subcommand:
          skrybe quotes --paste   Fetch random quote from localhost:3001/quote and paste at current cursor
          skrybe quotes --list    Planned
          skrybe quotes --add     Planned
        """
    }
}

do {
    try SkrybeCLI().run(arguments: CommandLine.arguments)
} catch {
    if let localized = error as? LocalizedError, let message = localized.errorDescription {
        fputs("❌ \(message)\n", stderr)
    } else {
        fputs("❌ \(error.localizedDescription)\n", stderr)
    }
    exit(EXIT_FAILURE)
}
