import Foundation
import Darwin
import CairnquireSync

// MARK: - CLI

enum CLIError: Error {
    case invalidArguments
    case missingServerURL
    case missingToken
    case fileNotFound
}

final class TerminalProgress: @unchecked Sendable {
    private let lock = NSLock()
    private var lastPercent = -1

    func update(_ progress: Double) {
        let percent = min(max(Int(progress * 100), 0), 100)

        lock.lock()
        defer { lock.unlock() }

        guard percent != lastPercent else { return }
        lastPercent = percent
        print("\rUploading... \(percent)%", terminator: "")
        fflush(stdout)
    }

    func finishLine() {
        print("")
    }
}

func readClipboard() -> String? {
    let task = Process()
    task.launchPath = "/usr/bin/pbpaste"
    let pipe = Pipe()
    task.standardOutput = pipe
    do {
        try task.run()
        let data = pipe.fileHandleForReading.readDataToEndOfFile()
        return String(data: data, encoding: .utf8)
    } catch {
        return nil
    }
}

struct CLI {
    static func run() async {
        let args = Array(CommandLine.arguments.dropFirst())
        
        do {
            try await execute(args: args)
        } catch {
            print("Error: \(error.localizedDescription)")
            exit(1)
        }
    }
    
    static func execute(args: [String]) async throws {
        guard let command = args.first else {
            printUsage()
            return
        }
        
        switch command {
        case "upload", "up":
            try await uploadCommand(args: Array(args.dropFirst()))
        case "note", "n":
            try await noteCommand(args: Array(args.dropFirst()))
        case "sync":
            try await syncCommand(args: Array(args.dropFirst()))
        case "config":
            try await configCommand(args: Array(args.dropFirst()))
        case "help", "-h", "--help":
            printHelp(for: args.dropFirst().first)
        default:
            print("Unknown command: \(command)")
            printUsage()
        }
    }
    
    // MARK: - Commands
    
    static func uploadCommand(args: [String]) async throws {
        if args.contains("--help") || args.contains("-h") {
            printUploadUsage()
            return
        }
        guard let filePath = args.first else {
            printUploadUsage()
            throw CLIError.invalidArguments
        }
        
        let config = ConfigStore.shared.load()
        guard let token = config.apiToken else {
            print("No API token configured. Run: cairnquire-cli config --token <token>")
            throw CLIError.missingToken
        }
        guard let baseURL = config.serverBaseURL else {
            print("No server URL configured. Run: cairnquire-cli config --server <url>")
            throw CLIError.missingServerURL
        }
        
        let fileURL = URL(fileURLWithPath: filePath)
        guard FileManager.default.fileExists(atPath: filePath) else {
            print("File not found: \(filePath)")
            throw CLIError.fileNotFound
        }
        
        let client = APIClient(baseURL: baseURL, apiToken: token)
        let manager = UploadManager(client: client, config: config)
        
        print("Uploading \(fileURL.lastPathComponent)...")
        let progress = TerminalProgress()
        let result = try await uploadFileResolvingDuplicates(manager: manager, fileURL: fileURL, progress: progress)
        progress.finishLine()
        
        print("✓ Uploaded successfully")
        print("  URL: \(result.url)")
        print("  Hash: \(result.hash)")
        print("  Copied to clipboard")
    }
    
    static func noteCommand(args: [String]) async throws {
        if args.contains("--help") || args.contains("-h") {
            printNoteUsage()
            return
        }
        let config = ConfigStore.shared.load()
        guard let token = config.apiToken else {
            print("No API token configured. Run: cairnquire-cli config --token <token>")
            throw CLIError.missingToken
        }
        guard let baseURL = config.serverBaseURL else {
            print("No server URL configured. Run: cairnquire-cli config --server <url>")
            throw CLIError.missingServerURL
        }
        
        let content: String
        if args.contains("--clipboard") || args.isEmpty {
            guard let clipboard = readClipboard() else {
                print("Clipboard is empty")
                throw CLIError.invalidArguments
            }
            content = clipboard
        } else {
            // Read from stdin or args
            if args.first == "-" {
                content = String(data: FileHandle.standardInput.availableData, encoding: .utf8) ?? ""
            } else {
                content = args.joined(separator: " ")
            }
        }
        
        guard !content.isEmpty else {
            print("No content provided")
            throw CLIError.invalidArguments
        }
        
        let title = args.first(where: { $0.hasPrefix("--title=") })?.replacingOccurrences(of: "--title=", with: "")
        
        let client = APIClient(baseURL: baseURL, apiToken: token)
        let manager = UploadManager(client: client, config: config)
        
        print("Creating note...")
        let result = try await manager.createNote(from: content, title: title)
        
        print("✓ Note created")
        print("  Path: \(result.path)")
        print("  Title: \(result.title)")
        print("  Hash: \(result.hash)")
    }
    
    static func syncCommand(args: [String]) async throws {
        if args.contains("--help") || args.contains("-h") {
            printSyncUsage()
            return
        }
        let config = ConfigStore.shared.load()
        guard let token = config.apiToken else {
            print("No API token configured")
            throw CLIError.missingToken
        }
        guard let baseURL = config.serverBaseURL else {
            print("No server URL configured")
            throw CLIError.missingServerURL
        }
        guard let syncFolder = config.syncFolder else {
            print("No sync folder configured")
            throw CLIError.invalidArguments
        }
        
        let client = APIClient(baseURL: baseURL, apiToken: token)
        let engine = SyncEngine(client: client, config: config)
        
        print("Syncing \(syncFolder)...")
        try await engine.performSync()
        print("✓ Sync complete")
    }
    
    static func configCommand(args: [String]) async throws {
        if args.contains("--help") || args.contains("-h") {
            printConfigUsage()
            return
        }

        var config = ConfigStore.shared.load()
        var i = 0
        while i < args.count {
            let arg = args[i]
            guard i + 1 < args.count else {
                print("Missing value for \(arg)")
                throw CLIError.invalidArguments
            }
            let value = args[i + 1]
            switch arg {
            case "--server":
                config.serverURL = value
            case "--token":
                config.apiToken = value
            case "--folder":
                config.syncFolder = value
            case "--upload-folder":
                config.defaultUploadFolder = value
            case "--new-note":
                guard let action = NewNoteAction(rawValue: value) else {
                    print("Invalid new note action: \(value). Use clipboard or editor.")
                    throw CLIError.invalidArguments
                }
                config.newNoteAction = action
            default:
                print("Unknown config option: \(arg)")
                throw CLIError.invalidArguments
            }
            i += 2
        }

        await ConfigStore.shared.save(config)

        print("Configuration saved:")
        print("  Server: \(config.serverURL)")
        print("  Sync Folder: \(config.syncFolder ?? "(none)")")
        print("  Upload Folder: \(config.defaultUploadFolder ?? "(none)")")
        print("  New Note: \(config.effectiveNewNoteAction.rawValue)")
        print("  Token: \(config.apiToken != nil ? "(set)" : "(not set)")")
    }

    static func uploadFileResolvingDuplicates(
        manager: UploadManager,
        fileURL: URL,
        progress: TerminalProgress
    ) async throws -> UploadResult {
        var duplicateAction: UploadDuplicateAction?

        while true {
            do {
                return try await manager.uploadFile(
                    fileURL,
                    duplicateAction: duplicateAction,
                    onProgress: { value in progress.update(value) }
                )
            } catch let error as APIError where error.isUploadConflict {
                progress.finishLine()
                duplicateAction = try promptForDuplicate(error, filename: fileURL.lastPathComponent)
                print("Uploading \(fileURL.lastPathComponent)...")
            }
        }
    }

    static func promptForDuplicate(_ error: APIError, filename: String) throws -> UploadDuplicateAction {
        if error.conflictType == "document" {
            print("A document named \(filename) already exists in the inbox.")
            if let existingPath = error.existingPath, !existingPath.isEmpty {
                print("Existing document: \(existingPath)")
            }
            print("[k] Keep both  [r] Replace existing  [c] Cancel")
            print("Choice: ", terminator: "")
            fflush(stdout)

            switch readLine()?.lowercased() {
            case "k", "keep", "keep-both":
                return .rename
            case "r", "replace", "overwrite":
                return .overwrite
            default:
                throw UploadCancelledError()
            }
        }

        print("\(filename) was already uploaded.")
        if let existingURL = error.existingUrl, !existingURL.isEmpty {
            print("Existing URL: \(existingURL)")
        }
        print("[u] Use existing URL  [c] Cancel")
        print("Choice: ", terminator: "")
        fflush(stdout)

        switch readLine()?.lowercased() {
        case "u", "use", "reuse":
            return .reuse
        default:
            throw UploadCancelledError()
        }
    }
    
    static func printUsage() {
        print("""
        Cairnquire CLI - macOS sync and upload tool
        
        USAGE:
            cairnquire-cli <command> [options]
        
        COMMANDS:
            upload, up <file>              Upload a file and copy URL to clipboard
            note, n [text]                 Create a new note from clipboard or arguments
            sync                           Sync the configured folder once
            config [options]               Configure the shared app and CLI settings
            help [command]                 Show general or command-specific help
        
        EXAMPLES:
            cairnquire-cli upload ~/Documents/report.pdf
            cairnquire-cli note --clipboard --title="Meeting Notes"
            echo "Hello World" | cairnquire-cli note -
            cairnquire-cli config --server http://localhost:8080 --token cq_pat_xxx
            cairnquire-cli sync

        Run 'cairnquire-cli help <command>' for details.
        """)
    }

    static func printHelp(for command: String?) {
        switch command {
        case "upload", "up":
            printUploadUsage()
        case "note", "n":
            printNoteUsage()
        case "sync":
            printSyncUsage()
        case "config":
            printConfigUsage()
        default:
            printUsage()
        }
    }

    static func printUploadUsage() {
        print("""
        USAGE:
            cairnquire-cli upload <file>

        Upload one file and copy its canonical Cairnquire URL to the clipboard.
        Readable text files become rendered documents. Other files remain attachments.
        If the inbox name or attachment content already exists, the CLI asks what to do.
        """)
    }

    static func printNoteUsage() {
        print("""
        USAGE:
            cairnquire-cli note [--clipboard] [--title="Title"]
            cairnquire-cli note <text>
            cairnquire-cli note -

        Create a markdown note from clipboard content, arguments, or stdin.
        """)
    }

    static func printSyncUsage() {
        print("""
        USAGE:
            cairnquire-cli sync

        Run one bidirectional sync for the folder configured with:
            cairnquire-cli config --folder <path>

        The menubar app also watches this folder when auto-sync is enabled in Settings.
        """)
    }

    static func printConfigUsage() {
        print("""
        USAGE:
            cairnquire-cli config [options]

        OPTIONS:
            --server <url>          Set the Cairnquire server URL
            --token <token>         Set the API token
            --folder <path>         Set the local folder used by sync
            --upload-folder <path>  Set the server folder for readable uploads
            --new-note <action>     Set New Note behavior: clipboard or editor

        The menubar app and CLI use the same saved configuration.
        """)
    }
}

// MARK: - Entry Point

@main
struct CairnquireCLI {
    static func main() async {
        await CLI.run()
    }
}
