const std = @import("std");

const escaper = @import("../../escaper.zig");

pub const models = @import("models.zig");

const log = std.log.scoped(.efa);

pub const Client = struct {
    pub const Error = error{
        InvalidResponse,
    };

    const Self = @This();

    const BodyLimit: usize = 10 * 1024 * 1024;

    allocator: std.mem.Allocator,
    efa_url: []const u8,
    efa_dm_url_str: []const u8,
    efa_stopfinder_url_str: []const u8,

    pub fn init(
        allocator: std.mem.Allocator,
        efa_url: []const u8,
    ) !Self {
        return .{
            .allocator = allocator,
            .efa_url = efa_url,
            .efa_dm_url_str = try std.fmt.allocPrint(
                allocator,
                "{s}/XML_DM_REQUEST?locationServerActive=1&stateless=1&sRaLP=1&itdLPxx_generalInfo=false&mode=direct&type_dm=any&itdLPxx_stopname=false&useRealtime=1&deleteAssignedStops_dm=1&depType=stopEvents&useAllStops=1&outputFormat=rapidJSON",
                .{efa_url},
            ),
            .efa_stopfinder_url_str = try std.fmt.allocPrint(
                allocator,
                "{s}/XML_STOPFINDER_REQUEST?doNotSearchForStops_sf=1&locationInfoActive=0&locationServerActive=0&sl3plusStopFinderMacro=1&type_sf=any&outputFormat=rapidJSON",
                .{efa_url},
            ),
        };
    }

    pub fn deinit(self: *const Self) void {
        self.allocator.free(self.efa_stopfinder_url_str);
        self.allocator.free(self.efa_dm_url_str);
    }

    pub fn findStops(self: *const Self, query: []const u8) !void {
        const escaped_query = try escaper.escapeUriComponent(self.allocator, query);
        defer if (escaped_query) |s| self.allocator.free(s);
        const url_query = escaped_query orelse query;

        var client = std.http.Client{ .allocator = self.allocator };
        defer client.deinit();

        const url_str = try std.fmt.allocPrint(
            self.allocator,
            "{s}&name_sf={s}",
            .{ self.efa_stopfinder_url_str, url_query },
        );
        defer self.allocator.free(url_str);
        const url = try std.Uri.parse(url_str);
        log.info("Finding stops ({s}): {s}", .{ query, url_str });

        var buf: [4096]u8 = undefined;
        var req = try client.open(.GET, url, .{
            .server_header_buffer = &buf,
        });
        defer req.deinit();

        try req.send();
        try req.finish();
        try req.wait();

        if (req.response.status != .ok)
            return Error.InvalidResponse;

        const body = try req.reader().readAllAlloc(self.allocator, BodyLimit);
        defer self.allocator.free(body);

        try std.io.getStdOut().writeAll(body);
    }

    pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(models.DmResponse) {
        const escaped_stop_id = try escaper.escapeUriComponent(self.allocator, stop_id);
        defer if (escaped_stop_id) |s| self.allocator.free(s);
        const url_stop_id = escaped_stop_id orelse stop_id;

        var client = std.http.Client{ .allocator = self.allocator };
        defer client.deinit();

        const url_str = try std.fmt.allocPrint(
            self.allocator,
            "{s}&name_dm={s}",
            .{ self.efa_dm_url_str, url_stop_id },
        );
        defer self.allocator.free(url_str);
        const url = try std.Uri.parse(url_str);
        log.info("Fetching departures: {s}", .{url_str});

        var buf: [4096]u8 = undefined;
        var req = try client.open(.GET, url, .{
            .server_header_buffer = &buf,
        });
        defer req.deinit();

        try req.send();
        try req.finish();
        try req.wait();

        if (req.response.status != .ok)
            return Error.InvalidResponse;

        const body = try req.reader().readAllAlloc(self.allocator, BodyLimit);
        defer self.allocator.free(body);

        const response = try std.json.parseFromSlice(
            models.DmResponse,
            self.allocator,
            body,
            .{ .allocate = .alloc_always, .ignore_unknown_fields = true },
        );

        // const str = try std.json.stringifyAlloc(self.allocator, response.value, .{});
        // defer self.allocator.free(str);
        // try std.io.getStdOut().writeAll(str);

        return response;
    }
};
