const std = @import("std");
const Config = @import("config.zig").Config;
const ws = @import("net/ws/client.zig");

/// Mode represents the operating mode of the CLI
pub const Mode = enum {
    /// Local/offline mode - runs execute locally, tracking to SQLite
    offline,
    /// Online/runner mode - jobs queue to remote server
    online,
};

/// DetectionResult includes the mode and any warning messages
pub const DetectionResult = struct {
    mode: Mode,
    warning: ?[]const u8,
};

/// Detect mode based on configuration and environment
/// Priority order (CLI — checked on every command):
/// 1. FETCHML_LOCAL=1 env var       → local (forced, skip ping)
/// 2. force_local=true in config    → local (forced, skip ping)
/// 3. cfg.Host == ""                → local (not configured)
/// 4. API ping within 2s timeout    → runner mode
///    - timeout / refused           → local (fallback, log once per session)
///    - 401/403                     → local (fallback, warn once about auth)
pub fn detect(allocator: std.mem.Allocator, cfg: Config) !DetectionResult {
    // Priority 1: FETCHML_LOCAL env var
    if (std.posix.getenv("FETCHML_LOCAL")) |val| {
        if (std.mem.eql(u8, val, "1")) {
            return .{ .mode = .offline, .warning = null };
        }
    }

    // Priority 2: force_local in config
    if (cfg.force_local) {
        return .{ .mode = .offline, .warning = null };
    }

    // Priority 3: No host configured
    if (cfg.worker_host.len == 0) {
        return .{ .mode = .offline, .warning = null };
    }

    // Priority 4: API ping with 2s timeout
    const ping_result = try pingServer(allocator, cfg, 2000);
    return switch (ping_result) {
        .success => .{ .mode = .online, .warning = null },
        .timeout => .{ .mode = .offline, .warning = "Server unreachable, falling back to local mode" },
        .refused => .{ .mode = .offline, .warning = "Server connection refused, falling back to local mode" },
        .auth_error => .{ .mode = .offline, .warning = "Authentication failed, falling back to local mode" },
    };
}

/// PingResult represents the outcome of a server ping
const PingResult = enum {
    success,
    timeout,
    refused,
    auth_error,
};

/// Ping the server with a timeout - simplified version that just tries to connect
fn pingServer(allocator: std.mem.Allocator, cfg: Config, timeout_ms: u64) !PingResult {
    _ = timeout_ms; // Timeout not implemented for this simplified version

    const ws_url = try cfg.getWebSocketUrl(allocator);
    defer allocator.free(ws_url);

    var connection = ws.Client.connect(allocator, ws_url, cfg.api_key) catch |err| {
        switch (err) {
            error.ConnectionTimedOut => return .timeout,
            error.ConnectionRefused => return .refused,
            error.AuthenticationFailed => return .auth_error,
            else => return .refused,
        }
    };
    defer connection.close();

    // Try to receive any message to confirm server is responding
    const response = connection.receiveMessage(allocator) catch |err| {
        switch (err) {
            error.ConnectionTimedOut => return .timeout,
            else => return .refused,
        }
    };
    defer allocator.free(response);

    return .success;
}

/// Check if mode is online
pub fn isOnline(mode: Mode) bool {
    return mode == .online;
}

/// Check if mode is offline
pub fn isOffline(mode: Mode) bool {
    return mode == .offline;
}

/// Require online mode, returning error if offline
pub fn requireOnline(mode: Mode, command_name: []const u8) !void {
    if (mode == .offline) {
        std.log.err("{s} requires server connection", .{command_name});
        return error.RequiresServer;
    }
}
