//! Alpha Vantage API provider -- used for ETF profiles (free endpoint).
//! API docs: https://www.alphavantage.co/documentation/
//!
//! Free tier: 25 requests/day. Only used for data other providers don't have.
//!
//! ETF Profile endpoint: GET /query?function=ETF_PROFILE&symbol=X&apikey=KEY
//!   Returns net assets, expense ratio, sector weights, top holdings, etc.

const std = @import("std");
const http = @import("../net/http.zig");
const RateLimiter = @import("../net/RateLimiter.zig");
const Date = @import("../Date.zig");
const EtfProfile = @import("../models/etf_profile.zig").EtfProfile;
const Holding = @import("../models/etf_profile.zig").Holding;
const SectorWeight = @import("../models/etf_profile.zig").SectorWeight;
const json_utils = @import("json_utils.zig");
const jsonStr = json_utils.jsonStr;

const base_url = "https://www.alphavantage.co/query";

/// Company overview data from Alpha Vantage OVERVIEW endpoint.
pub const CompanyOverview = struct {
    symbol: []const u8,
    name: ?[]const u8 = null,
    sector: ?[]const u8 = null,
    industry: ?[]const u8 = null,
    country: ?[]const u8 = null,
    market_cap: ?[]const u8 = null,
    asset_type: ?[]const u8 = null,
};

// -- Tests --

test "parseEtfProfileResponse basic" {
    const body =
        \\{
        \\  "net_assets": "323000000000",
        \\  "net_expense_ratio": "0.03",
        \\  "portfolio_turnover": "4.00",
        \\  "dividend_yield": "1.25",
        \\  "inception_date": "2010-09-09",
        \\  "leveraged": "NO",
        \\  "sectors": [
        \\    {"sector": "Technology", "weight": "31.50"},
        \\    {"sector": "Healthcare", "weight": "12.80"}
        \\  ],
        \\  "holdings": [
        \\    {"symbol": "AAPL", "description": "Apple Inc", "weight": "7.10"},
        \\    {"symbol": "MSFT", "description": "Microsoft Corp", "weight": "6.50"}
        \\  ]
        \\}
    ;

    const allocator = std.testing.allocator;
    const profile = try parseEtfProfileResponse(allocator, body, "VTI");

    // Clean up allocated slices
    defer {
        if (profile.sectors) |sectors| {
            for (sectors) |s| allocator.free(s.name);
            allocator.free(sectors);
        }
        if (profile.holdings) |holdings| {
            for (holdings) |h| {
                if (h.symbol) |s| allocator.free(s);
                allocator.free(h.name);
            }
            allocator.free(holdings);
        }
    }

    try std.testing.expectEqualStrings("VTI", profile.symbol);
    try std.testing.expectApproxEqAbs(@as(f64, 323000000000), profile.net_assets.?, 1.0);
    try std.testing.expectApproxEqAbs(@as(f64, 0.03), profile.expense_ratio.?, 0.001);
    try std.testing.expectApproxEqAbs(@as(f64, 4.0), profile.portfolio_turnover.?, 0.01);
    try std.testing.expectApproxEqAbs(@as(f64, 1.25), profile.dividend_yield.?, 0.01);
    try std.testing.expect(profile.inception_date != null);
    try std.testing.expect(!profile.leveraged);

    try std.testing.expectEqual(@as(usize, 2), profile.sectors.?.len);
    try std.testing.expectEqualStrings("Technology", profile.sectors.?[0].name);
    try std.testing.expectApproxEqAbs(@as(f64, 31.50), profile.sectors.?[0].weight, 0.01);

    try std.testing.expectEqual(@as(usize, 2), profile.holdings.?.len);
    try std.testing.expectEqualStrings("AAPL", profile.holdings.?[0].symbol.?);
    try std.testing.expectEqualStrings("Apple Inc", profile.holdings.?[0].name);
    try std.testing.expectEqual(@as(u32, 2), profile.total_holdings.?);
}

test "parseEtfProfileResponse leveraged ETF" {
    const body =
        \\{
        \\  "net_assets": "5000000000",
        \\  "leveraged": "YES",
        \\  "sectors": [],
        \\  "holdings": []
        \\}
    ;

    const allocator = std.testing.allocator;
    const profile = try parseEtfProfileResponse(allocator, body, "TQQQ");
    defer {
        if (profile.sectors) |s| allocator.free(s);
        if (profile.holdings) |h| allocator.free(h);
    }

    try std.testing.expect(profile.leveraged);
}

test "parseEtfProfileResponse error response" {
    const body =
        \\{"Error Message": "Invalid API call"}
    ;

    const allocator = std.testing.allocator;
    const result = parseEtfProfileResponse(allocator, body, "BAD");
    try std.testing.expectError(error.RequestFailed, result);
}

test "parseEtfProfileResponse rate limited" {
    const body =
        \\{"Note": "Thank you for using Alpha Vantage! Please visit..."}
    ;

    const allocator = std.testing.allocator;
    const result = parseEtfProfileResponse(allocator, body, "SPY");
    try std.testing.expectError(error.RateLimited, result);
}

test "parseCompanyOverview basic" {
    const body =
        \\{
        \\  "Symbol": "AAPL",
        \\  "Name": "Apple Inc",
        \\  "Sector": "Technology",
        \\  "Industry": "Consumer Electronics",
        \\  "Country": "USA",
        \\  "MarketCapitalization": "2900000000000",
        \\  "AssetType": "Common Stock"
        \\}
    ;

    const allocator = std.testing.allocator;
    const overview = try parseCompanyOverview(allocator, body, "AAPL");
    defer {
        if (overview.name) |n| allocator.free(n);
        if (overview.sector) |s| allocator.free(s);
        if (overview.industry) |i| allocator.free(i);
        if (overview.country) |c| allocator.free(c);
        if (overview.market_cap) |m| allocator.free(m);
        if (overview.asset_type) |a| allocator.free(a);
    }

    try std.testing.expectEqualStrings("AAPL", overview.symbol);
    try std.testing.expectEqualStrings("Apple Inc", overview.name.?);
    try std.testing.expectEqualStrings("Technology", overview.sector.?);
    try std.testing.expectEqualStrings("Consumer Electronics", overview.industry.?);
    try std.testing.expectEqualStrings("USA", overview.country.?);
    try std.testing.expectEqualStrings("2900000000000", overview.market_cap.?);
    try std.testing.expectEqualStrings("Common Stock", overview.asset_type.?);
}

test "parseCompanyOverview missing fields" {
    const body =
        \\{
        \\  "Symbol": "XYZ"
        \\}
    ;

    const allocator = std.testing.allocator;
    const overview = try parseCompanyOverview(allocator, body, "XYZ");

    try std.testing.expect(overview.name == null);
    try std.testing.expect(overview.sector == null);
    try std.testing.expect(overview.industry == null);
}

pub const AlphaVantage = struct {
    api_key: []const u8,
    client: http.Client,
    rate_limiter: RateLimiter,
    allocator: std.mem.Allocator,

    pub fn init(io: std.Io, allocator: std.mem.Allocator, api_key: []const u8) AlphaVantage {
        return .{
            .api_key = api_key,
            .client = http.Client.init(io, allocator),
            .rate_limiter = RateLimiter.perDay(io, 25),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *AlphaVantage) void {
        self.client.deinit();
    }

    /// Fetch company overview (sector, industry, country) for a stock symbol.
    pub fn fetchCompanyOverview(
        self: *AlphaVantage,
        allocator: std.mem.Allocator,
        symbol: []const u8,
    ) !CompanyOverview {
        self.rate_limiter.acquire();

        const url = try http.buildUrl(allocator, base_url, &.{
            .{ "function", "OVERVIEW" },
            .{ "symbol", symbol },
            .{ "apikey", self.api_key },
        });
        defer allocator.free(url);

        var response = try self.client.get(url);
        defer response.deinit();

        return parseCompanyOverview(allocator, response.body, symbol);
    }

    /// Fetch ETF profile data: expense ratio, holdings, sectors, etc.
    pub fn fetchEtfProfile(
        self: *AlphaVantage,
        allocator: std.mem.Allocator,
        symbol: []const u8,
    ) !EtfProfile {
        self.rate_limiter.acquire();

        const url = try http.buildUrl(allocator, base_url, &.{
            .{ "function", "ETF_PROFILE" },
            .{ "symbol", symbol },
            .{ "apikey", self.api_key },
        });
        defer allocator.free(url);

        var response = try self.client.get(url);
        defer response.deinit();

        return parseEtfProfileResponse(allocator, response.body, symbol);
    }
};

// -- JSON parsing --

fn parseEtfProfileResponse(
    allocator: std.mem.Allocator,
    body: []const u8,
    symbol: []const u8,
) !EtfProfile {
    const parsed = std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch
        return error.ParseError;
    defer parsed.deinit();

    const root = parsed.value.object;

    // Alpha Vantage returns {"Error Message": "..."} or {"Note": "..."} on error/rate limit
    if (root.get("Error Message")) |_| return error.RequestFailed;
    if (root.get("Note")) |_| return error.RateLimited;
    if (root.get("Information")) |_| return error.RateLimited;

    var profile = EtfProfile{
        .symbol = symbol,
    };

    if (root.get("net_assets")) |v| {
        profile.net_assets = parseStrFloat(v);
    }
    if (root.get("net_expense_ratio")) |v| {
        profile.expense_ratio = parseStrFloat(v);
    }
    if (root.get("portfolio_turnover")) |v| {
        profile.portfolio_turnover = parseStrFloat(v);
    }
    if (root.get("dividend_yield")) |v| {
        profile.dividend_yield = parseStrFloat(v);
    }
    if (root.get("inception_date")) |v| {
        if (jsonStr(v)) |s| {
            profile.inception_date = Date.parse(s) catch null;
        }
    }
    if (root.get("leveraged")) |v| {
        if (jsonStr(v)) |s| {
            profile.leveraged = std.mem.eql(u8, s, "YES");
        }
    }

    // Parse sectors
    if (root.get("sectors")) |sectors_val| {
        if (sectors_val == .array) {
            var sectors: std.ArrayList(SectorWeight) = .empty;
            errdefer sectors.deinit(allocator);

            for (sectors_val.array.items) |item| {
                const obj = switch (item) {
                    .object => |o| o,
                    else => continue,
                };
                const name = jsonStr(obj.get("sector")) orelse continue;
                const weight = parseStrFloat(obj.get("weight") orelse continue) orelse continue;

                const duped_name = try allocator.dupe(u8, name);
                try sectors.append(allocator, .{
                    .name = duped_name,
                    .weight = weight,
                });
            }
            profile.sectors = try sectors.toOwnedSlice(allocator);
        }
    }

    // Parse top holdings (limit to top 20 to keep output manageable)
    if (root.get("holdings")) |holdings_val| {
        if (holdings_val == .array) {
            const max_holdings: usize = 20;
            var holdings: std.ArrayList(Holding) = .empty;
            errdefer holdings.deinit(allocator);

            const total: u32 = @intCast(holdings_val.array.items.len);
            profile.total_holdings = total;

            const limit = @min(holdings_val.array.items.len, max_holdings);
            for (holdings_val.array.items[0..limit]) |item| {
                const obj = switch (item) {
                    .object => |o| o,
                    else => continue,
                };
                const desc = jsonStr(obj.get("description")) orelse continue;
                const weight = parseStrFloat(obj.get("weight") orelse continue) orelse continue;

                const duped_sym = if (jsonStr(obj.get("symbol"))) |s|
                    (try allocator.dupe(u8, s))
                else
                    null;
                const duped_name = try allocator.dupe(u8, desc);

                try holdings.append(allocator, .{
                    .symbol = duped_sym,
                    .name = duped_name,
                    .weight = weight,
                });
            }
            profile.holdings = try holdings.toOwnedSlice(allocator);
        }
    }

    return profile;
}

// -- Helpers --

fn parseStrFloat(val: ?std.json.Value) ?f64 {
    const v = val orelse return null;
    return switch (v) {
        .string => |s| std.fmt.parseFloat(f64, s) catch null,
        .float => |f| f,
        .integer => |i| @as(f64, @floatFromInt(i)),
        .null => null,
        else => null,
    };
}

fn parseCompanyOverview(
    allocator: std.mem.Allocator,
    body: []const u8,
    symbol: []const u8,
) !CompanyOverview {
    const parsed = std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch
        return error.ParseError;
    defer parsed.deinit();

    const root = parsed.value.object;

    if (root.get("Error Message")) |_| return error.RequestFailed;
    if (root.get("Note")) |_| return error.RateLimited;
    if (root.get("Information")) |_| return error.RateLimited;

    return .{
        .symbol = symbol,
        .name = if (jsonStr(root.get("Name"))) |s| allocator.dupe(u8, s) catch null else null,
        .sector = if (jsonStr(root.get("Sector"))) |s| allocator.dupe(u8, s) catch null else null,
        .industry = if (jsonStr(root.get("Industry"))) |s| allocator.dupe(u8, s) catch null else null,
        .country = if (jsonStr(root.get("Country"))) |s| allocator.dupe(u8, s) catch null else null,
        .market_cap = if (jsonStr(root.get("MarketCapitalization"))) |s| allocator.dupe(u8, s) catch null else null,
        .asset_type = if (jsonStr(root.get("AssetType"))) |s| allocator.dupe(u8, s) catch null else null,
    };
}
