#!/usr/bin/env python3
"""
Client generic pentru API-ul roa2web — autentificare + query-uri rapoarte.

Autentificare: user/parolă Oracle (ca la login-ul normal ROA) + 2FA email la
prima logare. După primul login cu trust_device=True, sesiunile următoare
folosesc refresh_token din keyring — fără OTP.

Credențiale (keyring, service "echo-core", vezi src/credential_store.py):
    roa2web_user, roa2web_pass, roa2web_server,
    roa2web_access_token, roa2web_refresh_token, roa2web_trusted_device_token

Firma implicită și base_url sunt în config.json -> "roa2web".

Usage:
    python3 tools/roa2web_client.py summary [--company COD_SAU_NUME] [--luna N] [--an N]
    python3 tools/roa2web_client.py companies
    python3 tools/roa2web_client.py get <path> [--company COD_SAU_NUME] [--param k=v ...]
    python3 tools/roa2web_client.py verify-2fa <cod> <email>
"""

import argparse
import json
import sys
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit

import requests

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

from src.credential_store import get_secret, set_secret
from src.config import Config


class ROA2WebError(Exception):
    pass


class ROA2WebClient:
    def __init__(self):
        self.config = Config()
        self.base_url = self.config.get("roa2web.base_url", "https://roa2web.romfast.ro/roa2web/api")
        self.session = requests.Session()
        self._token = get_secret("roa2web_access_token")

        parsed = urlsplit(self.base_url)
        self._origin = f"{parsed.scheme}://{parsed.netloc}"
        # IIS/ARR reverse-proxy-ul din fata backend-ului serveste totul sub /roa2web,
        # dar FastAPI nu stie de acest prefix -> redirect-urile lui (307, ex. trailing
        # slash pe rutele definite la "/") vin fara el si pica in 404. Il reinjectam.
        self._prefix = parsed.path[: -len("/api")] if parsed.path.endswith("/api") else parsed.path

    @property
    def default_company(self) -> str:
        return self.config.get("roa2web.default_company", "114")

    def _store_tokens(self, data: dict) -> None:
        set_secret("roa2web_access_token", data["access_token"])
        if data.get("refresh_token"):
            set_secret("roa2web_refresh_token", data["refresh_token"])
        if data.get("trusted_device_token"):
            set_secret("roa2web_trusted_device_token", data["trusted_device_token"])
        self._token = data["access_token"]

    def _login_password(self) -> None:
        user = get_secret("roa2web_user")
        pw = get_secret("roa2web_pass")
        server = get_secret("roa2web_server") or "romfast"
        if not user or not pw:
            raise ROA2WebError("Lipsesc roa2web_user/roa2web_pass din keyring (eco secrets set ...)")

        payload = {"username": user, "password": pw, "server_id": server}
        trusted = get_secret("roa2web_trusted_device_token")
        if trusted:
            payload["trusted_device_token"] = trusted

        r = self.session.post(f"{self.base_url}/auth/login", json=payload, timeout=15)
        r.raise_for_status()
        data = r.json()
        if data.get("requires_2fa"):
            raise ROA2WebError(
                f"Necesar cod OTP nou, trimis pe {data.get('masked_email')}. "
                "Cere-i lui Marius codul, apoi ruleaza verify_2fa(code, email)."
            )
        self._store_tokens(data)

    def _refresh(self) -> bool:
        refresh_token = get_secret("roa2web_refresh_token")
        if not refresh_token:
            return False
        r = self.session.post(
            f"{self.base_url}/auth/refresh",
            json={"refresh_token": refresh_token},
            timeout=15,
        )
        if r.status_code != 200:
            return False
        self._store_tokens(r.json())
        return True

    def verify_2fa(self, code: str, email: str, server_id: str | None = None) -> dict:
        server = server_id or get_secret("roa2web_server") or "romfast"
        r = self.session.post(
            f"{self.base_url}/auth/verify-2fa-code",
            json={"code": code, "email": email, "server_id": server, "trust_device": True},
            timeout=15,
        )
        r.raise_for_status()
        data = r.json()
        self._store_tokens(data)
        return data

    def request(self, method: str, path: str, params: dict | None = None, _retried: bool = False) -> dict:
        if not self._token and not self._refresh():
            self._login_password()

        url = f"{self.base_url}{path}" if path.startswith("/") else f"{self.base_url}/{path}"
        headers = {"Authorization": f"Bearer {self._token}"}
        r = self.session.request(method, url, params=params, headers=headers, timeout=30, allow_redirects=False)

        if r.status_code in (307, 308) and "Location" in r.headers:
            loc = urlsplit(r.headers["Location"])
            loc_path = loc.path
            if self._prefix and not loc_path.startswith(self._prefix):
                loc_path = self._prefix + loc_path
            fixed_url = urlunsplit((urlsplit(self.base_url).scheme, urlsplit(self.base_url).netloc, loc_path, loc.query, ""))
            r = self.session.request(method, fixed_url, headers=headers, timeout=30)

        if r.status_code == 401 and not _retried:
            if self._refresh() or (self._login_password() or True):
                return self.request(method, path, params=params, _retried=True)
        r.raise_for_status()
        return r.json()

    def get(self, path: str, params: dict | None = None) -> dict:
        return self.request("GET", path, params=params)

    def companies(self) -> list:
        return self.get("/companies").get("companies", [])

    def dashboard_summary(self, company: str | None = None, luna: int | None = None, an: int | None = None) -> dict:
        params = {"company": company or self.default_company}
        if luna:
            params["luna"] = luna
        if an:
            params["an"] = an
        return self.get("/reports/dashboard/summary", params)


def resolve_company(client: ROA2WebClient, value: str | None) -> str:
    """Acceptă cod numeric sau (parte din) numele firmei; implicit firma din config."""
    if not value:
        return client.default_company
    if value.isdigit():
        return value
    needle = value.lower()
    matches = [c for c in client.companies() if needle in c["name"].lower()]
    if not matches:
        raise ROA2WebError(f"Firma '{value}' nu a fost gasita in lista de firme.")
    if len(matches) > 1:
        names = ", ".join(f"{c['id_firma']}={c['name']}" for c in matches)
        raise ROA2WebError(f"'{value}' e ambiguu, se potriveste cu mai multe firme: {names}")
    return str(matches[0]["id_firma"])


def main():
    parser = argparse.ArgumentParser(description="Client CLI pentru roa2web API")
    sub = parser.add_subparsers(dest="cmd", required=True)

    p = sub.add_parser("summary", help="Dashboard financiar (trezorerie, clienti, furnizori)")
    p.add_argument("--company", help="Cod sau nume firma (implicit: cea din config.json)")
    p.add_argument("--luna", type=int)
    p.add_argument("--an", type=int)

    sub.add_parser("companies", help="Lista firmelor disponibile")

    p = sub.add_parser("get", help="Query generic pe orice endpoint /api/...")
    p.add_argument("path", help='ex: "/reports/invoices" sau "/reports/treasury/bank-cash-register"')
    p.add_argument("--company", help="Daca e dat, se adauga ca param 'company'")
    p.add_argument("--param", action="append", default=[], metavar="k=v", help="Parametru query, repetabil")

    v = sub.add_parser("verify-2fa", help="Finalizeaza login-ul cu un cod OTP primit pe email")
    v.add_argument("code")
    v.add_argument("email")

    args = parser.parse_args()
    client = ROA2WebClient()

    try:
        if args.cmd == "summary":
            company = resolve_company(client, args.company)
            data = client.dashboard_summary(company=company, luna=args.luna, an=args.an)
        elif args.cmd == "companies":
            for c in client.companies():
                print(f"{c['id_firma']}\t{c['name']}")
            return
        elif args.cmd == "get":
            params = dict(kv.split("=", 1) for kv in args.param)
            if args.company:
                params["company"] = resolve_company(client, args.company)
            data = client.get(args.path, params or None)
        elif args.cmd == "verify-2fa":
            raw = client.verify_2fa(args.code, args.email)
            data = {k: v for k, v in raw.items() if k not in ("access_token", "refresh_token", "trusted_device_token")}
    except ROA2WebError as e:
        print(f"Eroare: {e}", file=sys.stderr)
        sys.exit(1)

    print(json.dumps(data, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
