const std = @import("std");
const fs = std.fs;
const Allocator = std.mem.Allocator;
const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
pub const Entry = @import("ssh-cache/Entry.zig");
pub const DiskCache = @import("ssh-cache/DiskCache.zig");

pub const Options = struct {
    clear: bool = false,
    add: ?[]const u8 = null,
    remove: ?[]const u8 = null,
    host: ?[]const u8 = null,
    @"expire-days": ?u32 = null,

    pub fn deinit(self: *Options) void {
        _ = self;
    }

    pub fn help(self: Options) !void {
        _ = self;
        return Action.help_error;
    }
};

/// Manage the SSH terminfo cache for automatic remote host setup.
///
/// When SSH integration is enabled with `shell-integration-features = ssh-terminfo`,
/// Ghostty automatically installs its terminfo on remote hosts. This command
/// manages the cache of successful installations to avoid redundant uploads.
///
/// The cache stores hostnames (or user@hostname combinations) along with timestamps.
/// Entries older than the expiration period are automatically removed during cache
/// operations. By default, entries never expire.
///
/// Only one of `--clear`, `--add`, `--remove`, or `--host` can be specified.
/// If multiple are specified, one of the actions will be executed but
/// it isn't guaranteed which one. This is entirely unsafe so you should split
/// multiple actions into separate commands.
///
/// Examples:
///   ghostty +ssh-cache                          # List all cached hosts
///   ghostty +ssh-cache --host=example.com       # Check if host is cached
///   ghostty +ssh-cache --add=example.com        # Manually add host to cache
///   ghostty +ssh-cache --add=user@example.com   # Add user@host combination
///   ghostty +ssh-cache --remove=example.com     # Remove host from cache
///   ghostty +ssh-cache --clear                  # Clear entire cache
///   ghostty +ssh-cache --expire-days=30         # Set custom expiration period
pub fn run(alloc_gpa: Allocator) !u8 {
    var arena = std.heap.ArenaAllocator.init(alloc_gpa);
    defer arena.deinit();
    const alloc = arena.allocator();

    var opts: Options = .{};
    defer opts.deinit();

    {
        var iter = try args.argsIterator(alloc_gpa);
        defer iter.deinit();
        try args.parse(Options, alloc_gpa, &opts, &iter);
    }

    var stdout_buffer: [1024]u8 = undefined;
    var stdout_file: std.fs.File = .stdout();
    var stdout_writer = stdout_file.writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    var stderr_buffer: [1024]u8 = undefined;
    var stderr_file: std.fs.File = .stderr();
    var stderr_writer = stderr_file.writer(&stderr_buffer);
    const stderr = &stderr_writer.interface;

    const result = runInner(alloc, opts, stdout, stderr);

    // Flushing *shouldn't* fail but...
    stdout.flush() catch {};
    stderr.flush() catch {};
    return result;
}

pub fn runInner(
    alloc: Allocator,
    opts: Options,
    stdout: *std.Io.Writer,
    stderr: *std.Io.Writer,
) !u8 {
    // Setup our disk cache to the standard location
    const cache_path = try DiskCache.defaultPath(alloc, "ghostty");
    const cache: DiskCache = .{ .path = cache_path };

    if (opts.clear) {
        try cache.clear();
        try stdout.print("Cache cleared.\n", .{});
        return 0;
    }

    if (opts.add) |host| {
        const result = cache.add(alloc, host) catch |err| switch (err) {
            DiskCache.Error.HostnameIsInvalid => {
                try stderr.print("Error: Invalid hostname format '{s}'\n", .{host});
                try stderr.print("Expected format: hostname or user@hostname\n", .{});
                return 1;
            },
            DiskCache.Error.CacheIsLocked => {
                try stderr.print("Error: Cache is busy, try again\n", .{});
                return 1;
            },
            else => {
                try stderr.print(
                    "Error: Unable to add '{s}' to cache. Error: {}\n",
                    .{ host, err },
                );
                return 1;
            },
        };

        switch (result) {
            .added => try stdout.print("Added '{s}' to cache.\n", .{host}),
            .updated => try stdout.print("Updated '{s}' cache entry.\n", .{host}),
        }
        return 0;
    }

    if (opts.remove) |host| {
        cache.remove(alloc, host) catch |err| switch (err) {
            DiskCache.Error.HostnameIsInvalid => {
                try stderr.print("Error: Invalid hostname format '{s}'\n", .{host});
                try stderr.print("Expected format: hostname or user@hostname\n", .{});
                return 1;
            },
            DiskCache.Error.CacheIsLocked => {
                try stderr.print("Error: Cache is busy, try again\n", .{});
                return 1;
            },
            else => {
                try stderr.print(
                    "Error: Unable to remove '{s}' from cache. Error: {}\n",
                    .{ host, err },
                );
                return 1;
            },
        };
        try stdout.print("Removed '{s}' from cache.\n", .{host});
        return 0;
    }

    if (opts.host) |host| {
        const cached = cache.contains(alloc, host) catch |err| switch (err) {
            error.HostnameIsInvalid => {
                try stderr.print("Error: Invalid hostname format '{s}'\n", .{host});
                try stderr.print("Expected format: hostname or user@hostname\n", .{});
                return 1;
            },
            else => {
                try stderr.print(
                    "Error: Unable to check host '{s}' in cache. Error: {}\n",
                    .{ host, err },
                );
                return 1;
            },
        };

        if (cached) {
            try stdout.print(
                "'{s}' has Ghostty terminfo installed.\n",
                .{host},
            );
            return 0;
        } else {
            try stdout.print(
                "'{s}' does not have Ghostty terminfo installed.\n",
                .{host},
            );
            return 1;
        }
    }

    // Default action: list all hosts
    var entries = try cache.list(alloc);
    defer DiskCache.deinitEntries(alloc, &entries);
    try listEntries(alloc, &entries, stdout);
    return 0;
}

fn listEntries(
    alloc: Allocator,
    entries: *const std.StringHashMap(Entry),
    writer: *std.Io.Writer,
) !void {
    if (entries.count() == 0) {
        try writer.print("No hosts in cache.\n", .{});
        return;
    }

    // Sort entries by hostname for consistent output
    var items: std.ArrayList(Entry) = .empty;
    defer items.deinit(alloc);

    var iter = entries.iterator();
    while (iter.next()) |kv| {
        try items.append(alloc, kv.value_ptr.*);
    }

    std.mem.sort(Entry, items.items, {}, struct {
        fn lessThan(_: void, a: Entry, b: Entry) bool {
            return std.mem.lessThan(u8, a.hostname, b.hostname);
        }
    }.lessThan);

    try writer.print("Cached hosts ({d}):\n", .{items.items.len});
    const now = std.time.timestamp();

    for (items.items) |entry| {
        const age_days = @divTrunc(now - entry.timestamp, std.time.s_per_day);
        if (age_days == 0) {
            try writer.print("  {s} (today)\n", .{entry.hostname});
        } else if (age_days == 1) {
            try writer.print("  {s} (yesterday)\n", .{entry.hostname});
        } else {
            try writer.print("  {s} ({d} days ago)\n", .{ entry.hostname, age_days });
        }
    }
}

test {
    _ = DiskCache;
    _ = Entry;
}
