//! Fidelity reconciler (pure compute).
//!
//! Fidelity exports a single "all accounts" positions CSV. Parsing
//! lives in `brokerage/fidelity.zig`; this module wires that parser
//! into the shared per-account comparison engine in `common.zig`.
//! Because the Fidelity export is a plain per-account positions list,
//! the only Fidelity-specific knowledge here is "use the Fidelity CSV
//! parser" and "the institution key is `fidelity`" - the comparison is
//! shared, and the ANSI display lives in `commands/audit/`.

const std = @import("std");
const zfin = @import("../../root.zig");
const analysis = @import("../../analytics/analysis.zig");
const Date = @import("../../Date.zig");
const common = @import("common.zig");
const fidelity_parser = @import("../../brokerage/fidelity.zig");

/// Parse a Fidelity positions CSV and reconcile it against the
/// portfolio. Returns owned `AccountComparison` results (free each
/// `.comparisons` slice, then the results slice). String fields in
/// the results borrow from `csv_data`, which must outlive them.
///
/// Propagates the parser's errors (`EmptyFile` / `UnexpectedHeader`)
/// and allocation failures so the caller can decide between a
/// user-facing message (explicit `--fidelity`) and silently skipping
/// the file (flagless auto-reconcile).
pub fn reconcile(
    allocator: std.mem.Allocator,
    portfolio: zfin.Portfolio,
    csv_data: []const u8,
    account_map: analysis.AccountMap,
    prices: std.StringHashMap(f64),
    as_of: Date,
) ![]common.AccountComparison {
    const positions = try fidelity_parser.parseCsv(allocator, csv_data);
    // The result strings borrow from `csv_data`, not from this slice,
    // so freeing the slice array here is safe.
    defer allocator.free(positions);
    return common.compareAccounts(allocator, portfolio, positions, account_map, "fidelity", prices, as_of);
}

// ── Tests ────────────────────────────────────────────────────

const portfolio_mod = @import("../../models/portfolio.zig");

test "reconcile: parses Fidelity CSV and matches against portfolio" {
    const allocator = std.testing.allocator;

    const csv =
        "Account Number,Account Name,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Today's Gain/Loss Dollar,Today's Gain/Loss Percent,Total Gain/Loss Dollar,Total Gain/Loss Percent,Percent Of Account,Cost Basis Total,Average Cost Basis,Type\n" ++
        "Z123,Individual,AAPL,APPLE INC,100,$150.00,+$2.00,$15000.00,+$200.00,+1.35%,+$5000.00,+50.00%,100%,$10000.00,$100.00,Margin,\n";

    var lots = [_]portfolio_mod.Lot{
        .{ .symbol = "AAPL", .shares = 100, .open_date = Date.fromYmd(2024, 1, 1), .open_price = 100.0, .account = "Sample Brokerage" },
    };
    const portfolio = portfolio_mod.Portfolio{ .lots = &lots, .allocator = allocator };

    var entries = [_]analysis.AccountTaxEntry{
        .{ .account = "Sample Brokerage", .tax_type = .taxable, .institution = "fidelity", .account_number = "Z123" },
    };
    const acct_map = analysis.AccountMap{ .entries = &entries, .allocator = allocator };

    var prices = std.StringHashMap(f64).init(allocator);
    defer prices.deinit();
    try prices.put("AAPL", 150.0);

    const results = try reconcile(allocator, portfolio, csv, acct_map, prices, Date.fromYmd(2026, 5, 8));
    defer {
        for (results) |r| allocator.free(r.comparisons);
        allocator.free(results);
    }

    try std.testing.expectEqual(@as(usize, 1), results.len);
    try std.testing.expectEqualStrings("Sample Brokerage", results[0].account_name);
    // 100 shares @ $150 on both sides -> no discrepancy.
    try std.testing.expect(!results[0].has_discrepancies);
}

test "reconcile: propagates parser errors" {
    const allocator = std.testing.allocator;
    const portfolio = portfolio_mod.Portfolio{ .lots = &.{}, .allocator = allocator };
    var entries = [_]analysis.AccountTaxEntry{};
    const acct_map = analysis.AccountMap{ .entries = &entries, .allocator = allocator };
    var prices = std.StringHashMap(f64).init(allocator);
    defer prices.deinit();

    try std.testing.expectError(
        error.EmptyFile,
        reconcile(allocator, portfolio, "", acct_map, prices, Date.fromYmd(2026, 5, 8)),
    );
}
