"""Workflow: Stammdaten-Pflege für Lieferanten/Custom-Companies.

Aufruf:
  python companies.py list [--filter Amazon] [--type CUSTOM_SUPPLIER]
  python companies.py add --name "Acme GmbH" --country 81 [--city Berlin --iban DE... --tags "eu,preferred"]
  python companies.py update --uid 12345 --name "Acme AG" [--city München]
  python companies.py delete --uid 12345 [--confirm --confirm-name "Acme AG"]
  python companies.py suggest --portal-name "Server4You" --portal-url https://... --download-url https://...

DELETE-Flow:
  1. Vorab-Preview via getOneCompany: bricht ab, wenn die Company nicht existiert.
  2. Bestätigung Pflicht:
     - Interaktiv (TTY): exakter Name muss getippt werden (nicht y/yes).
     - Non-interaktiv: --confirm UND --confirm-name "<exakter Name>" nötig.
  3. Audit-Zeile auf stderr nach erfolgreichem DELETE.

Kein Banking-Gate. Re-Read nach add/update verifiziert, welche Felder die API
tatsächlich gespeichert hat. Felder, die stillschweigend verschluckt wurden,
werden als Warnung ausgegeben.
"""
from __future__ import annotations

import argparse
import datetime as _dt
import json
import sys
from pathlib import Path
from typing import Any

try:
    sys.stdout.reconfigure(encoding="utf-8")
except (AttributeError, Exception):
    pass

sys.path.insert(0, str(Path(__file__).parent.parent))

from gmi_client import GmiClient, GmiHttpError  # noqa: E402


# ---------- Fetcher ----------

def list_companies(
    client: GmiClient,
    name_filter: str | None = None,
    type_filter: str | None = None,
) -> dict:
    try:
        r = client.call("getCompanyList")
    except GmiHttpError as e:
        return {"ok": False, "error": f"http_{e.status}", "detail": str(e)}
    items = r if isinstance(r, list) else []
    if type_filter:
        items = [c for c in items if (c.get("companyType") or "") == type_filter]
    if name_filter:
        nf = name_filter.lower()
        items = [c for c in items if nf in (c.get("name") or "").lower()]
    return {"ok": True, "count": len(items), "companies": items}


def get_one_company(client: GmiClient, uid: int) -> dict | None:
    """Liefert die Company als dict, oder None wenn sie nicht existiert.

    API-Eigenheit: getOneCompany liefert ein dict bei Treffer, aber ein leeres
    list `[]` wenn die UID nicht existiert. Diese Helper normalisiert das.
    """
    try:
        r = client.call("getOneCompany", companyUid=uid)
    except GmiHttpError as e:
        if e.status == 404:
            return None
        raise
    if isinstance(r, dict):
        return r
    if isinstance(r, list):
        return r[0] if r else None
    return None


# ---------- Build request bodies ----------

_COMPANY_FIELDS = [
    "name", "countryUid", "tags", "street", "zip", "city", "email", "phone",
    "fax", "taxNumber", "vatId", "commercialRegister", "iban", "bic", "url",
    "creditorNumber",
]


def _build_company_body(args: argparse.Namespace, include_portal_id: bool) -> dict:
    body: dict[str, Any] = {}
    if args.name is not None:
        body["name"] = args.name
    if args.country is not None:
        body["countryUid"] = args.country
    if args.tags is not None:
        body["tags"] = [t.strip() for t in args.tags.split(",") if t.strip()]
    for k in ("street", "zip", "city", "email", "phone", "fax", "vat_id",
              "tax_number", "commercial_register", "iban", "bic", "url",
              "creditor_number"):
        val = getattr(args, k, None)
        if val is not None:
            # map snake_case CLI to camelCase API fields
            api_key = {
                "vat_id": "vatId",
                "tax_number": "taxNumber",
                "commercial_register": "commercialRegister",
                "creditor_number": "creditorNumber",
            }.get(k, k)
            body[api_key] = val
    if include_portal_id and getattr(args, "portal_id", None) is not None:
        body["portalId"] = args.portal_id
    return body


# ---------- Write operations ----------

def add_company(client: GmiClient, args: argparse.Namespace) -> dict:
    body = _build_company_body(args, include_portal_id=True)
    if "name" not in body:
        return {"ok": False, "error": "missing_name"}
    try:
        created = client.call("addCustomCompany", _body=body)
    except GmiHttpError as e:
        return {"ok": False, "error": f"http_{e.status}", "detail": str(e)}
    uid = created.get("companyUid") if isinstance(created, dict) else None
    verification = _verify_body_fields(client, uid, body) if uid else {}
    return {
        "ok": True,
        "action": "add",
        "response": created,
        "companyUid": uid,
        **verification,
    }


def update_company(client: GmiClient, args: argparse.Namespace) -> dict:
    body = _build_company_body(args, include_portal_id=False)
    if "name" not in body:
        return {"ok": False, "error": "missing_name"}
    try:
        resp = client.call("updateCustomCompany", companyUid=args.uid, _body=body)
    except GmiHttpError as e:
        return {"ok": False, "error": f"http_{e.status}", "detail": str(e)}
    verification = _verify_body_fields(client, args.uid, body)
    return {
        "ok": True,
        "action": "update",
        "response": resp,
        "companyUid": args.uid,
        **verification,
    }


def _verify_body_fields(client: GmiClient, uid: int | None, body: dict) -> dict:
    """Liest die Company neu und prüft, welche übergebenen Felder nicht
    zurückkommen. Die API schluckt einige Felder paketabhängig still.
    """
    if not uid:
        return {}
    try:
        fresh = get_one_company(client, uid)
    except GmiHttpError:
        return {"verified": False}
    if not fresh:
        return {"verified": False, "warning": "re-read returned empty"}
    missing: list[str] = []
    for k, v in body.items():
        if k == "tags":
            continue  # Tag-Vergleich separat, siehe unten
        if fresh.get(k) in (None, "", 0) and v not in (None, "", 0):
            missing.append(k)
    out: dict[str, Any] = {"verified": True, "fresh": fresh}
    if missing:
        out["silently_dropped"] = missing
    return out


def delete_company(
    client: GmiClient,
    uid: int,
    confirm_name: str | None,
    interactive: bool,
) -> dict:
    # Layer 1: Preview-Pflicht
    preview = get_one_company(client, uid)
    if preview is None:
        return {"ok": False, "error": "not_found", "companyUid": uid}

    actual_name = preview.get("name") or ""

    # Layer 2: Bestätigung Pflicht
    if interactive:
        prompt = (f"\nZu löschen: companyUid={uid}  name={actual_name!r}\n"
                  f"Zur Bestätigung bitte den EXAKTEN Namen eintippen: ")
        try:
            typed = input(prompt)
        except EOFError:
            return {"ok": False, "error": "not_confirmed", "reason": "no_tty_input"}
        if typed.strip() != actual_name:
            return {"ok": False, "error": "name_mismatch", "expected": actual_name,
                    "companyUid": uid}
    else:
        if not confirm_name:
            return {"ok": False, "error": "missing_confirm_name",
                    "hint": "Non-interaktiv --confirm UND --confirm-name '<name>' setzen",
                    "companyUid": uid, "expected": actual_name}
        if confirm_name != actual_name:
            return {"ok": False, "error": "name_mismatch", "expected": actual_name,
                    "got": confirm_name, "companyUid": uid}

    # Execute
    try:
        resp = client.call("deleteCustomCompany", companyUid=uid)
    except GmiHttpError as e:
        return {"ok": False, "error": f"http_{e.status}", "detail": str(e),
                "companyUid": uid}

    # Audit auf stderr, nicht stdout — kein Key, keine Secrets
    ts = _dt.datetime.now().isoformat(timespec="seconds")
    print(f"[DELETE] companies/{uid} \"{actual_name}\" at {ts}", file=sys.stderr)

    return {"ok": True, "action": "delete", "companyUid": uid, "name": actual_name,
            "response": resp}


def suggest_company(
    client: GmiClient,
    portal_name: str,
    portal_url: str,
    download_url: str,
    comment: str | None = None,
) -> dict:
    body: dict[str, Any] = {
        "portal_name": portal_name,
        "portal_url": portal_url,
        "download_url": download_url,
    }
    if comment:
        body["comment"] = comment
    try:
        resp = client.call("suggestNewCompany", _body=body)
    except GmiHttpError as e:
        return {"ok": False, "error": f"http_{e.status}", "detail": str(e)}
    return {"ok": True, "action": "suggest", "response": resp}


# ---------- Formatting ----------

def format_companies_md(data: dict) -> str:
    items = data.get("companies", [])
    header = f"## Lieferanten ({data.get('count', 0)})"
    if not items:
        return header + "\n\n(keine Treffer)"
    lines = [header, "",
             "| companyUid | Name | Typ | Land | Creditor |",
             "|---|---|---|---|---|"]
    for c in items:
        lines.append("| {uid} | {name} | {typ} | {c} | {cr} |".format(
            uid=c.get("companyUid", ""),
            name=c.get("name", ""),
            typ=c.get("companyType", ""),
            c=c.get("countryCode", ""),
            cr=c.get("creditorNumber") or "",
        ))
    return "\n".join(lines)


def format_write_md(data: dict) -> str:
    action = data.get("action", "?")
    lines = [f"## {action} — OK", ""]
    if data.get("companyUid"):
        lines.append(f"- **companyUid:** {data['companyUid']}")
    if data.get("name"):
        lines.append(f"- **name:** {data['name']}")
    if data.get("silently_dropped"):
        lines.append(f"- **Warnung:** API hat folgende Felder nicht übernommen: "
                     f"{', '.join(data['silently_dropped'])}")
    if data.get("response"):
        lines.append("")
        lines.append("```json")
        lines.append(json.dumps(data["response"], indent=2, ensure_ascii=False))
        lines.append("```")
    return "\n".join(lines)


def format_error_md(data: dict) -> str:
    return f"## Fehler\n\n```json\n{json.dumps(data, indent=2, ensure_ascii=False, default=str)}\n```"


# ---------- CLI ----------

def _add_company_write_args(parser: argparse.ArgumentParser, name_required: bool, with_portal: bool) -> None:
    parser.add_argument("--name", required=name_required, help="Name der Company (Pflicht)")
    parser.add_argument("--country", type=int, help="countryUid (int)")
    parser.add_argument("--tags", help="Kommagetrennte Tag-Liste")
    parser.add_argument("--street")
    parser.add_argument("--zip")
    parser.add_argument("--city")
    parser.add_argument("--email")
    parser.add_argument("--phone")
    parser.add_argument("--fax")
    parser.add_argument("--tax-number", dest="tax_number")
    parser.add_argument("--vat-id", dest="vat_id")
    parser.add_argument("--commercial-register", dest="commercial_register")
    parser.add_argument("--iban")
    parser.add_argument("--bic")
    parser.add_argument("--url")
    parser.add_argument("--creditor-number", dest="creditor_number")
    if with_portal:
        parser.add_argument("--portal-id", dest="portal_id", type=int)


def _print(data: dict, args: argparse.Namespace) -> None:
    if args.format == "json":
        print(json.dumps(data, indent=2, ensure_ascii=False, default=str))
        return
    if not data.get("ok"):
        print(format_error_md(data))
        return
    if "companies" in data:
        print(format_companies_md(data))
    else:
        print(format_write_md(data))


def main() -> int:
    common = argparse.ArgumentParser(add_help=False)
    common.add_argument("--format", choices=["json", "markdown"], default="markdown")

    p = argparse.ArgumentParser(parents=[common])
    sub = p.add_subparsers(dest="cmd", required=True)

    lp = sub.add_parser("list", parents=[common], help="Lieferanten listen")
    lp.add_argument("--filter", dest="name_filter", help="Substring-Filter auf Name")
    lp.add_argument("--type", dest="type_filter", help="z.B. CUSTOM_SUPPLIER")

    ap = sub.add_parser("add", parents=[common], help="Neuen Custom-Supplier anlegen")
    _add_company_write_args(ap, name_required=True, with_portal=True)

    up = sub.add_parser("update", parents=[common], help="Custom-Supplier aktualisieren")
    up.add_argument("--uid", type=int, required=True, help="companyUid")
    _add_company_write_args(up, name_required=True, with_portal=False)

    dp = sub.add_parser("delete", parents=[common], help="Custom-Supplier löschen (Confirm-Pflicht)")
    dp.add_argument("--uid", type=int, required=True, help="companyUid")
    dp.add_argument("--confirm", action="store_true",
                    help="Non-interaktiv: zusammen mit --confirm-name setzen")
    dp.add_argument("--confirm-name", dest="confirm_name",
                    help="Exakter Name der Company, muss matchen")

    sp = sub.add_parser("suggest", parents=[common],
                        help="Feature-Request an GetMyInvoices (neues Portal vorschlagen)")
    sp.add_argument("--portal-name", dest="portal_name", required=True)
    sp.add_argument("--portal-url", dest="portal_url", required=True)
    sp.add_argument("--download-url", dest="download_url", required=True)
    sp.add_argument("--comment")

    args = p.parse_args()

    try:
        client = GmiClient()
    except Exception as e:
        print(json.dumps({"error": "config", "detail": str(e)}))
        return 1

    try:
        if args.cmd == "list":
            data = list_companies(client, args.name_filter, args.type_filter)
        elif args.cmd == "add":
            data = add_company(client, args)
        elif args.cmd == "update":
            data = update_company(client, args)
        elif args.cmd == "delete":
            interactive = sys.stdin.isatty() and not args.confirm
            data = delete_company(
                client, args.uid,
                confirm_name=args.confirm_name,
                interactive=interactive,
            )
        elif args.cmd == "suggest":
            data = suggest_company(
                client, args.portal_name, args.portal_url, args.download_url,
                comment=args.comment,
            )
        else:
            print(json.dumps({"error": "unknown_command", "cmd": args.cmd}))
            return 1
    except GmiHttpError as e:
        print(json.dumps({"error": "api", "status": e.status, "detail": str(e)}))
        return 1

    _print(data, args)
    return 0 if data.get("ok") else 1


if __name__ == "__main__":
    sys.exit(main())
