#!/usr/bin/env bash
# CA3-ready monthly TVA summary for Arcodange — collectée − déductible = net.
#
# Usage:
#   tva-summary.sh [--year YYYY] [--since YYYY-MM-DD] [--until YYYY-MM-DD]
#
# Composes:
#   - customer-side invoice lines (TVA collectée, CA3 A1 / A4 / E2)
#   - supplier-side invoice lines (TVA déductible, CA3 19 / 20 / 17+24)
# Outputs one table per month plus a cumulative net line.
#
# Net interpretation:
#   - net > 0 → TVA à reverser à l'État
#   - net < 0 → crédit de TVA, demande de remboursement / report
#   - net = 0 → équilibre

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh"

SINCE=""; UNTIL=""; YEAR=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --since) SINCE="$2"; shift 2 ;;
    --until) UNTIL="$2"; shift 2 ;;
    --year)  YEAR="$2"; SINCE="$2-01-01"; UNTIL="$2-12-31"; shift 2 ;;
    -h|--help) sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
    *) echo "tva-summary.sh: unknown arg: $1" >&2; exit 2 ;;
  esac
done

WORK="$(mktemp -d -t tvasum.XXXXXX)"
trap 'rm -rf "${WORK}"' EXIT

# 1. Customer side (invoices)
"${DOL_CURL}" '/invoices?limit=500&sortfield=t.datef&sortorder=ASC' > "${WORK}/inv.json"
IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1])) if r.get('id')))" "${WORK}/inv.json")
SOCIDS=$(python3 -c "import json,sys; print(' '.join(sorted({str(r.get('socid')) for r in json.load(open(sys.argv[1])) if r.get('socid')})))" "${WORK}/inv.json")
mkdir -p "${WORK}/inv" "${WORK}/tp"
for id in ${IDS};    do "${DOL_CURL}" "/invoices/${id}"     > "${WORK}/inv/${id}.json"; done
for s  in ${SOCIDS}; do "${DOL_CURL}" "/thirdparties/${s}"  > "${WORK}/tp/${s}.json"; done

# 2. Supplier side (supplier invoices)
"${DOL_CURL}" '/supplierinvoices?limit=500' > "${WORK}/sinv.json"
SIDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1])) if r.get('id')))" "${WORK}/sinv.json")
SSOCIDS=$(python3 -c "import json,sys; print(' '.join(sorted({str(r.get('socid')) for r in json.load(open(sys.argv[1])) if r.get('socid')})))" "${WORK}/sinv.json")
mkdir -p "${WORK}/sinv"
for id in ${SIDS};   do "${DOL_CURL}" "/supplierinvoices/${id}" > "${WORK}/sinv/${id}.json"; done
for s in ${SSOCIDS}; do
  [[ -f "${WORK}/tp/${s}.json" ]] || "${DOL_CURL}" "/thirdparties/${s}" > "${WORK}/tp/${s}.json"
done

python3 - "${WORK}" "${SINCE}" "${UNTIL}" "${YEAR}" <<'PY'
import json, sys, os, collections, datetime
work, since, until, year_arg = sys.argv[1:5]

EU = set("AT BE BG HR CY CZ DK EE FI DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE".split())

# Thirdparty country map
tp_cnty = {}
for fn in os.listdir(os.path.join(work, "tp")):
    try: d = json.load(open(os.path.join(work, "tp", fn)))
    except json.JSONDecodeError: continue
    tp_cnty[fn[:-len(".json")]] = d.get("country_code") or ""

def in_window(ts):
    if not ts: return False
    d = datetime.date.fromtimestamp(int(ts))
    if since and d < datetime.date.fromisoformat(since): return False
    if until and d > datetime.date.fromisoformat(until): return False
    return True

# Customer-side buckets
def bucket_collected(country, tx):
    if tx and tx > 0:
        return f"A1+8 ({tx}% collectée)"
    if country == "FR":           return "A1 0% (FR atypique)"
    if country in EU:             return "A4 (autoliquidation intra-UE)"
    if country:                   return "E2 (export hors UE)"
    return "(country missing)"

# Supplier-side buckets
def bucket_deductible(country, tx):
    if tx and tx > 0:             return f"ligne 19/20 ({tx}% déductible)"
    if country == "FR":           return "FR exempt / HT seulement"
    if country in EU:             return "ligne 17+24 (autoliquidation intra-UE)"
    if country:                   return "ligne 7 (import hors UE)"
    return "(country missing)"

# Aggregate per month
months = collections.defaultdict(lambda: {
    "collected": {"ht": 0.0, "tva": 0.0, "by_bucket": collections.defaultdict(lambda: {"ht":0.0,"tva":0.0})},
    "deductible":{"ht": 0.0, "tva": 0.0, "by_bucket": collections.defaultdict(lambda: {"ht":0.0,"tva":0.0})},
})

# Customer side
for fn in sorted(os.listdir(os.path.join(work, "inv"))):
    try: inv = json.load(open(os.path.join(work, "inv", fn)))
    except json.JSONDecodeError: continue
    ts = int(inv.get("date") or 0)
    if not in_window(ts): continue
    ym  = datetime.date.fromtimestamp(ts).strftime("%Y-%m")
    sid = str(inv.get("socid") or "")
    cnty = tp_cnty.get(sid, "")
    for line in inv.get("lines") or []:
        tx  = round(float(line.get("tva_tx")  or 0), 4)
        ht  = float(line.get("total_ht")  or 0)
        tva = float(line.get("total_tva") or 0)
        b   = bucket_collected(cnty, tx)
        months[ym]["collected"]["ht"]  += ht
        months[ym]["collected"]["tva"] += tva
        months[ym]["collected"]["by_bucket"][b]["ht"]  += ht
        months[ym]["collected"]["by_bucket"][b]["tva"] += tva

# Supplier side
for fn in sorted(os.listdir(os.path.join(work, "sinv"))):
    try: inv = json.load(open(os.path.join(work, "sinv", fn)))
    except json.JSONDecodeError: continue
    ts = int(inv.get("date") or 0)
    if not in_window(ts): continue
    ym  = datetime.date.fromtimestamp(ts).strftime("%Y-%m")
    sid = str(inv.get("socid") or "")
    cnty = tp_cnty.get(sid, "")
    lines = inv.get("lines") or []
    if not lines:
        ht = float(inv.get("total_ht") or 0); tva = float(inv.get("total_tva") or 0)
        tx = round((tva / ht * 100), 4) if ht else 0.0
        lines = [{"tva_tx": tx, "total_ht": ht, "total_tva": tva}]
    for line in lines:
        tx  = round(float(line.get("tva_tx")  or 0), 4)
        ht  = float(line.get("total_ht")  or 0)
        tva = float(line.get("total_tva") or 0)
        b   = bucket_deductible(cnty, tx)
        months[ym]["deductible"]["ht"]  += ht
        months[ym]["deductible"]["tva"] += tva
        months[ym]["deductible"]["by_bucket"][b]["ht"]  += ht
        months[ym]["deductible"]["by_bucket"][b]["tva"] += tva

scope = f"window={since or '-inf'} → {until or '+inf'}"
if year_arg: scope = f"year {year_arg}"

print(f"# Arcodange TVA monthly summary — {scope}")
print(f"# (composition of dolibarr-tva-reconciliation + dolibarr-tva-deductible)")
print()

cum_collected = cum_deductible = 0.0
for ym in sorted(months):
    m = months[ym]
    col_tva = m["collected"]["tva"]
    ded_tva = m["deductible"]["tva"]
    net     = col_tva - ded_tva
    cum_collected  += col_tva
    cum_deductible += ded_tva

    print(f"=== {ym} ===")
    print(f"  Customer side (TVA collectée)   basis HT={m['collected']['ht']:>10.2f}   TVA={col_tva:>8.2f}")
    for b, s in sorted(m["collected"]["by_bucket"].items()):
        print(f"      {b:<42}  HT={s['ht']:>10.2f}  TVA={s['tva']:>8.2f}")
    print(f"  Supplier side (TVA déductible)  basis HT={m['deductible']['ht']:>10.2f}   TVA={ded_tva:>8.2f}")
    for b, s in sorted(m["deductible"]["by_bucket"].items()):
        print(f"      {b:<42}  HT={s['ht']:>10.2f}  TVA={s['tva']:>8.2f}")
    if abs(net) < 0.005:
        verdict = "(équilibre)"
    elif net > 0:
        verdict = f"→ TVA À REVERSER : {net:.2f} €"
    else:
        verdict = f"→ CRÉDIT DE TVA : {-net:.2f} €"
    print(f"  --- Net du mois : collectée − déductible = {col_tva:.2f} − {ded_tva:.2f} = {net:>8.2f}   {verdict}")
    print()

cum_net = cum_collected - cum_deductible
print(f"=== CUMUL {scope} ===")
print(f"  TVA collectée totale  : {cum_collected:>10.2f}")
print(f"  TVA déductible totale : {cum_deductible:>10.2f}")
print(f"  Net cumulé            : {cum_net:>10.2f}", end="")
if abs(cum_net) < 0.005:
    print("   (équilibre)")
elif cum_net > 0:
    print(f"   → TVA À REVERSER cumulée : {cum_net:.2f} €")
else:
    print(f"   → CRÉDIT DE TVA cumulé : {-cum_net:.2f} €")
PY
