#!/usr/bin/env python3
"""
BofA CSV -> hledger journal converter for Brixton Branding LLC.
Run: python3 categorize.py boa-2026-ytd.csv > brixton-2026.journal
"""

import csv, re, sys
from datetime import datetime

BANK_ACCOUNT = "Assets:Checking:BofA"


def parse_amount(s):
    return float(s.replace(',', '').replace('"', '').strip())


def extract_bnf(desc):
    """Extract the beneficiary company name from a wire description."""
    m = re.search(r'BNF:([A-Z][^\s][^:]{2,40?}?)(?:\s+(?:BNF BK:|PMT DET:|ID:))', desc.upper())
    if m:
        name = m.group(1).strip().rstrip(',').title()
        return name
    # Fallback: grab text after BNF: up to next keyword
    m = re.search(r'BNF:(.+?)(?:\s+(?:BNF BK|PMT DET|ID:|$))', desc, re.IGNORECASE)
    if m:
        return m.group(1).strip().rstrip(',')
    return "International vendor"


def classify(desc):
    d = desc.upper()

    # ── INCOME ────────────────────────────────────────────────────────────────
    if "WINGMAN SOLUTIONS" in d:
        return "Income:Sales:Wingman", "Wingman Solutions LLC"
    if "GREENER STANDARDS" in d:
        return "Income:Sales:GreenerStandards", "Greener Standards Inc"
    if "PLANET X SMOKE" in d:
        return "Income:Sales:PlanetX", "Planet X Smoke Shop"
    if "TTT EXTRACTIONS" in d:
        return "Income:Sales:TTTExtractions", "TTT Extractions No. 2"
    if "XINHUA COUNTY XIANGDA" in d and "WIRE IN" in d:
        return "Income:Returns", "Xinhua County Xiangda - returned wire"
    if "ZELLE PAYMENT FROM" in d:
        return "Income:Other", "Zelle income - Transport"

    # ── COGS ──────────────────────────────────────────────────────────────────
    if "OC WHOLESALE PACKAGING" in d:
        return "Expenses:COGS:Packaging", "OC Wholesale Packaging"
    if "J & J COLLECTIONS" in d or "J &J COLLECTIONS" in d:
        return "Expenses:COGS:Packaging", "J & J Collections LLC"
    if "BEAST COAST PACKAGING" in d:
        return "Expenses:COGS:Packaging", "Beast Coast Packaging"
    if "BOLD PRINT SHOP" in d:
        return "Expenses:COGS:Merchandise", "Bold Print Shop LLC"
    if "ABSTRAX TECH" in d:
        return "Expenses:COGS:Product", "Abstrax Tech"
    if "RAINIER VALLEY HEMP" in d:
        return "Expenses:COGS:Product", "Rainier Valley Hemp"
    if "HEMP OIL DISTRO" in d:
        return "Expenses:COGS:Product", "Hemp Oil Distro LLC"
    if "DISTURBED DIMENSI" in d:
        return "Expenses:COGS:Product", "Disturbed Dimensions"
    if "MED FOR AMERICA" in d:
        return "Expenses:COGS:Product", "Med for America Inc"
    if "THE PH DEPOT" in d:
        return "Expenses:COGS:Product", "The PH Depot LLC"
    if "SHENZHEN CHENGTIANYI" in d:
        return "Expenses:COGS:Inventory", "Shenzhen Chengtianyi Technology"
    # International inventory -- explicit company names (Ghost: payee = company name)
    if "POOFEE LIMITED" in d:
        return "Expenses:COGS:Inventory", "Poofee Limited"
    if "SHENZEN TALINA" in d or "SHENZHEN TALINA" in d:
        return "Expenses:COGS:Inventory", "Shenzen Talina"
    if "HONGKONG AIRUISI" in d or "HONG KONG AIRUISI" in d:
        return "Expenses:COGS:Inventory", "Hongkong Airuisi Electronics"
    if "HELIXUS TRADING" in d:
        return "Expenses:COGS:Inventory", "Helixus Trading Co."
    if "SHENZEN SINAI" in d or "SHENZHEN SINAI" in d:
        return "Expenses:COGS:Inventory", "Shenzen Sinai Global Tech"
    if "ANHUI PADHAO" in d:
        return "Expenses:COGS:Inventory", "Anhui Padhao"
    if "SHENZHEN KUNHAO" in d:
        return "Expenses:COGS:Inventory", "Shenzhen Kunhao Technology"
    if "ODWRS CO" in d:
        return "Expenses:COGS:Inventory", "ODWRS Co."
    # Catch-all for remaining intl wires (POP GOODS bank label or INTL WIRE OUT)
    if "POP GOOD" in d or "INTL WIRE OUT" in d:
        return "Expenses:COGS:Inventory", extract_bnf(desc)

    # ── PAYROLL ───────────────────────────────────────────────────────────────
    if "ADP WAGE PAY" in d:
        return "Expenses:Payroll:Wages", "ADP Wage Pay"
    if "ADP TAX" in d:
        return "Expenses:Payroll:Taxes", "ADP Payroll Tax"
    if "ADP PAY-BY-PAY" in d:
        return "Expenses:Payroll:WorkersComp", "ADP Pay-By-Pay (Workers Comp)"
    if "ADP PAYROLL FEES" in d or "ADP FEES" in d:
        return "Expenses:Payroll:AdminFees", "ADP Payroll Admin Fee"

    # ── RENT ──────────────────────────────────────────────────────────────────
    if "VICTOR RENDANO" in d:
        return "Expenses:Rent", "Victor Rendano - Warehouse Rent"
    if "N.TRIPHAMMER" in d or "TRIPHAMMER" in d:
        return "Expenses:Rent", "Brixton - Triphammer Rent"

    # ── CONTRACTORS ───────────────────────────────────────────────────────────
    if "BARRETT TARR" in d:
        return "Expenses:Contractors", "Barrett Tarr"
    if "JPG CONTRACTOR" in d:
        return "Expenses:Contractors", "JPG Contractor Inc"
    if "DANIEL CARMEL" in d:
        return "Expenses:Contractors", "Daniel Carmel"
    if "KYLER NICKEL" in d:
        return "Expenses:Contractors", "Kyler Nickel"

    # ── PROFESSIONAL SERVICES ─────────────────────────────────────────────────
    if "BOWERS" in d:
        return "Expenses:Professional:Accounting", "Bowers (Accounting Firm)"

    # ── OWNER DRAW ────────────────────────────────────────────────────────────
    if "ALEXANDER DALSEY" in d:
        return "Equity:OwnerDraw", "Owner Draw - Alexander Dalsey"

    # ── TRAVEL ────────────────────────────────────────────────────────────────
    if any(x in d for x in ["HILTON HOTEL GARAGE"]):
        return "Expenses:Travel:Transportation", "Parking"
    if any(x in d for x in ["HILTON", "WESTIN COPLEY FD", "HOTEL RES",
                              "HOLIDAY INN", "HOTELBOOKING", "EXPEDIA"]):
        return "Expenses:Travel:Hotels", "Hotel / Lodging"
    if "WESTIN" in d and "FD" not in d:
        return "Expenses:Travel:Hotels", "Hotel / Lodging"
    if "DELTA AIR" in d:
        return "Expenses:Travel:Flights", "Delta Air"
    if "UBER" in d:
        return "Expenses:Travel:Transportation", "Uber"
    if "4020 STATE ROU" in d:
        return "Expenses:Travel:Gas", "Gas - Travel"

    # ── MEALS ─────────────────────────────────────────────────────────────────
    if any(x in d for x in ["TST*O YA", "LEGAL SEA FOODS"]):
        return "Expenses:Meals", "Business Meal"
    if "WESTIN COPLEY FD" in d:
        return "Expenses:Meals", "Business Meal - Westin Copley"

    # ── SUPPLIES: GENERAL ─────────────────────────────────────────────────────
    if "ULINE" in d:
        return "Expenses:Supplies:General", "Uline"
    if "EARTHWISE PACKAGING" in d:
        return "Expenses:Supplies:General", "Earthwise Packaging"
    if "TAP PLASTICS" in d:
        return "Expenses:Supplies:General", "TAP Plastics"
    if "GLOBALINDUSTRIALEQ" in d or "GIH*GLOBAL" in d:
        return "Expenses:Supplies:General", "Global Industrial"
    if "STAPLES" in d:
        return "Expenses:Supplies:General", "Staples"
    if "BEST BUY" in d:
        return "Expenses:Supplies:General", "Best Buy"
    if "AMAZON" in d:
        return "Expenses:Supplies:General", "Amazon"
    if "PLAUD" in d:
        return "Expenses:Software", "Plaud.ai"
    if "USPS" in d:
        return "Expenses:Supplies:Postage", "USPS"

    # ── INSURANCE ─────────────────────────────────────────────────────────────
    if "ANTHEM BLUE" in d:
        return "Expenses:Insurance:Health", "Anthem Blue Cross"

    # ── UTILITIES ─────────────────────────────────────────────────────────────
    if "NY STATE ELECTRIC" in d or "NYSEG" in d:
        return "Expenses:Utilities:Electric", "NY State Electric & Gas"
    if "VERIZON" in d:
        return "Expenses:Utilities:Phone", "Verizon Wireless"

    # ── BANK FEES ─────────────────────────────────────────────────────────────
    if "WIRE TRANSFER FEE" in d:
        return "Expenses:BankFees:WireFees", "BofA Wire Transfer Fee"
    if "EXTERNAL TRANSFER FEE" in d:
        return "Expenses:BankFees:TransferFees", "BofA External Transfer Fee"

    # ── LEGAL / FILING ────────────────────────────────────────────────────────
    if "CORPORATE FILINGS" in d:
        return "Expenses:Legal:FilingFees", "Corporate Filings LLC"

    # ── CREDIT CARD PAYMENTS ──────────────────────────────────────────────────
    if "ONLINE BANKING PAYMENT TO CRD" in d:
        return "Liabilities:CreditCard", "Credit card payment"

    return "Expenses:Uncategorized", desc[:60]


def convert(csv_path):
    with open(csv_path, newline='', encoding='utf-8-sig') as f:
        content = f.read()

    lines = content.split('\n')
    header_idx = next(i for i, l in enumerate(lines) if l.startswith('Date,'))
    reader = csv.reader(lines[header_idx:])
    next(reader)  # skip header

    print("; Brixton Branding LLC - BofA Checking")
    print("; Generated by Clawstin / hledger")
    print()
    accounts = [
        "Assets:Checking:BofA",
        "Income:Sales:Wingman", "Income:Sales:GreenerStandards",
        "Income:Sales:PlanetX", "Income:Sales:TTTExtractions",
        "Income:Returns", "Income:Other",
        "Expenses:COGS:Packaging", "Expenses:COGS:Inventory",
        "Expenses:COGS:Product", "Expenses:COGS:Merchandise",
        "Expenses:Payroll:Wages", "Expenses:Payroll:Taxes",
        "Expenses:Payroll:WorkersComp", "Expenses:Payroll:AdminFees",
        "Expenses:Rent", "Expenses:Contractors",
        "Expenses:Professional:Accounting",
        "Expenses:Travel:Hotels", "Expenses:Travel:Flights",
        "Expenses:Travel:Transportation", "Expenses:Travel:Gas",
        "Expenses:Meals",
        "Expenses:Supplies:General", "Expenses:Supplies:Postage",
        "Expenses:Insurance:Health",
        "Expenses:Utilities:Electric", "Expenses:Utilities:Phone",
        "Expenses:BankFees:WireFees", "Expenses:BankFees:TransferFees",
        "Expenses:Legal:FilingFees", "Expenses:Software", "Expenses:Uncategorized",
        "Equity:OwnerDraw", "Liabilities:CreditCard",
    ]
    for a in accounts:
        print(f"account {a}")
    print()

    for row in reader:
        if len(row) < 3 or not row[0] or row[0] == 'Date':
            continue
        date_str, desc, amt_str = row[0], row[1], row[2]
        if not amt_str:
            continue
        try:
            amt = parse_amount(amt_str)
            dt = datetime.strptime(date_str, '%m/%d/%Y')
        except Exception:
            continue

        account, payee = classify(desc)
        date_fmt = dt.strftime('%Y-%m-%d')

        if amt >= 0:
            print(f"{date_fmt} {payee}")
            print(f"    {BANK_ACCOUNT:<45}   {amt:>12,.2f}")
            print(f"    {account:<45}  {-amt:>12,.2f}")
        else:
            print(f"{date_fmt} {payee}")
            print(f"    {account:<45}   {abs(amt):>12,.2f}")
            print(f"    {BANK_ACCOUNT:<45}  {amt:>12,.2f}")
        print()


if __name__ == '__main__':
    path = sys.argv[1] if len(sys.argv) > 1 else 'boa-2026-ytd.csv'
    convert(path)
