#!/usr/bin/env python3
"""
Procesare bon fiscal: PDF → OCR API → SQLite API → Oracle

Usage:
    python process_bon.py <pdf_path> [--save]
    
    --save  Salvează efectiv în Oracle (altfel dry run)

Fluxul:
    1. OCR extract via API (http://10.0.20.171:8000/api/data-entry/ocr/extract)
    2. Save receipt via API (http://10.0.20.171:8000/api/data-entry/receipts/) - TOATE datele
    3. Save to Oracle:
       - Verifică/creează partener
       - Verifică TVA la încasare (CALENDAR.TVA_INCASARE)
       - Generează note contabile corecte
"""

import sys
import os
import json
import time
import argparse
from pathlib import Path
from datetime import datetime
from decimal import Decimal

import requests
import oracledb
from dotenv import load_dotenv

# Load .env from parent directory
load_dotenv(Path(__file__).parent.parent / ".env")

# === CONFIG ===
API_BASE = "http://10.0.20.171:8000"
API_USER = "MARIUS M"
API_PASS = os.getenv("ROA_API_PASSWORD", "")
SERVER_ID = "central"
COMPANY_ID = 110  # MARIUSM AUTO

ORACLE_CONFIG = {
    "user": "MARIUSM_AUTO",
    "password": os.getenv("ORACLE_PASSWORD", ""),
    "dsn": "10.0.20.121:1521/ROA"
}

# Mapare CUI → cont cheltuială
CUI_TO_CONT = {
    "11201891": "6022",   # MOL
    "1590082": "6022",    # OMV Petrom
    "14991381": "6022",   # Rompetrol
    "10562600": "6021",   # Dedeman / Five Holding (Brick)
    "1879865": "6021",    # Five Holding
}

# Mapare cotă TVA → (ID_JTVA baza, ID_JTVA tva, TAXCODE, TAXCODE_TVAI)
# Pentru achiziții interne neexigibile (TVA la încasare)
JTVA_NEEX = {
    21: (210, 211, 301104, 301305),  # ACH. INT. NEEX. 21%
    19: (188, 189, 301101, 301301),  # ACH. INT. NEEX. 19%
    11: (214, 215, 301105, 301306),  # ACH. INT. NEEX. 11%
    9: (172, 173, 301102, 301302),   # ACH. INT. NEEX. 9%
    5: (174, 175, 301103, 301303),   # ACH. INT. NEEX. 5%
}

# Pentru achiziții interne normale (fără TVA la încasare)
JTVA_NORMAL = {
    21: (208, 209, 301104, 301305),  # ACH. INT. 21%
    19: (None, None, 301101, 301301),
    9: (None, None, 301102, 301302),
}


def get_cont(cui: str) -> str:
    """Mapare CUI → cont cheltuială."""
    cui_clean = (cui or "").upper().replace("RO", "").strip()
    return CUI_TO_CONT.get(cui_clean, "6028")  # 6028 = alte cheltuieli


class APIClient:
    """Client pentru roa2web API."""
    
    def __init__(self, base_url: str):
        self.base_url = base_url.rstrip("/")
        self.token = None
        self.session = requests.Session()
    
    def login(self, username: str, password: str, server_id: str) -> bool:
        """Login și obține token."""
        r = self.session.post(
            f"{self.base_url}/api/auth/login",
            json={"username": username, "password": password, "server_id": server_id}
        )
        if r.status_code == 200:
            data = r.json()
            self.token = data.get("access_token")
            self.session.headers["Authorization"] = f"Bearer {self.token}"
            return True
        print(f"Login failed: {r.status_code} - {r.text}")
        return False
    
    def ocr_extract(self, file_path: Path) -> dict:
        """Submit OCR job și așteaptă rezultatul."""
        # Determine mime type
        suffix = file_path.suffix.lower()
        if suffix == ".pdf":
            mime_type = "application/pdf"
        elif suffix in (".jpg", ".jpeg"):
            mime_type = "image/jpeg"
        elif suffix == ".png":
            mime_type = "image/png"
        else:
            # Try to detect from content
            with open(file_path, "rb") as f:
                header = f.read(8)
            if header[:4] == b'%PDF':
                mime_type = "application/pdf"
                suffix = ".pdf"
            elif header[:3] == b'\xff\xd8\xff':
                mime_type = "image/jpeg"
                suffix = ".jpg"
            elif header[:8] == b'\x89PNG\r\n\x1a\n':
                mime_type = "image/png"
                suffix = ".png"
            else:
                mime_type = "application/pdf"  # default
                suffix = ".pdf"
        
        # Use proper filename with extension
        filename = file_path.stem + suffix if not file_path.suffix else file_path.name
        
        # Submit
        with open(file_path, "rb") as f:
            r = self.session.post(
                f"{self.base_url}/api/data-entry/ocr/extract",
                files={"file": (filename, f, mime_type)}
            )
        if r.status_code != 200:
            return {"success": False, "error": f"OCR submit failed: {r.text}"}
        
        job_id = r.json().get("job_id")
        print(f"  OCR job: {job_id}")
        
        # Wait for result (max 60s per request, retry if pending)
        for _ in range(4):  # Max 4 retries = ~240s total
            r = self.session.get(
                f"{self.base_url}/api/data-entry/ocr/jobs/{job_id}/wait",
                params={"timeout": 60, "wait_for_terminal": "true"},
                timeout=70
            )
            if r.status_code != 200:
                return {"success": False, "error": f"OCR wait failed: {r.text}"}
            
            data = r.json()
            status = data.get("status")
            
            if status == "completed":
                return {"success": True, "result": data.get("result"), "time_ms": data.get("processing_time_ms")}
            elif status == "failed":
                return {"success": False, "error": data.get("error") or "OCR failed"}
            # Still pending/processing - will retry
        
        return {"success": False, "error": "OCR timeout"}
    
    def create_receipt(self, ocr_result: dict, company_id: int) -> dict:
        """Creează receipt în SQLite via API cu TOATE datele."""
        # Parse date
        date_str = ocr_result.get("receipt_date")
        if date_str:
            receipt_date = date_str[:10]  # YYYY-MM-DD
        else:
            receipt_date = datetime.now().strftime("%Y-%m-%d")
        
        # Build TVA breakdown from OCR
        tva_breakdown = []
        for tva_entry in (ocr_result.get("tva_entries") or []):
            tva_breakdown.append({
                "code": tva_entry.get("code"),
                "percent": tva_entry.get("percent"),
                "amount": float(tva_entry.get("amount") or 0)
            })
        
        # Build payment methods from OCR
        payment_methods = []
        for pm in (ocr_result.get("payment_methods") or []):
            payment_methods.append({
                "method": pm.get("method"),
                "amount": float(pm.get("amount") or 0)
            })
        
        # Determine payment mode
        payment_mode = ocr_result.get("suggested_payment_mode") or "casa"
        # If has CARD payment, it's "banca"
        if any(pm.get("method", "").upper() == "CARD" for pm in payment_methods):
            payment_mode = "banca"
        elif any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods):
            payment_mode = "casa"
        
        payload = {
            "receipt_type": "bon_fiscal",
            "direction": "cheltuiala",
            "receipt_number": ocr_result.get("receipt_number"),
            "receipt_series": ocr_result.get("receipt_series"),
            "receipt_date": receipt_date,
            "amount": float(ocr_result.get("amount") or 0),
            "partner_name": ocr_result.get("partner_name"),
            "cui": ocr_result.get("cui"),
            "tva_total": float(ocr_result.get("tva_total") or 0),
            "tva_breakdown": tva_breakdown if tva_breakdown else None,
            "payment_methods": payment_methods if payment_methods else None,
            "payment_mode": payment_mode,
            "company_id": company_id,
            "vendor_address": ocr_result.get("address"),
            "items_count": ocr_result.get("items_count"),
            "ocr_raw_text": ocr_result.get("raw_text"),
        }
        
        # Remove None values
        payload = {k: v for k, v in payload.items() if v is not None}
        
        self.session.headers["X-Selected-Company"] = str(company_id)
        r = self.session.post(
            f"{self.base_url}/api/data-entry/receipts/",
            json=payload
        )
        
        if r.status_code in (200, 201):
            return {"success": True, "receipt": r.json()}
        else:
            return {"success": False, "error": f"Create receipt failed: {r.text}"}


def get_or_create_partner(cursor, cui: str, name: str, address: str = None) -> int:
    """Găsește sau creează partener în Oracle. Returnează ID_PART."""
    cui_clean = (cui or "").upper().replace("RO", "").strip()
    
    if not cui_clean:
        return 0  # No CUI, no partner
    
    # Try to find existing partner
    cursor.execute("""
        SELECT ID_PART FROM NOM_PARTENERI 
        WHERE COD_FISCAL = :cui OR COD_FISCAL = :cui2
    """, cui=cui_clean, cui2="RO" + cui_clean)
    row = cursor.fetchone()
    
    if row:
        return row[0]  # Found existing partner
    
    # Create new partner
    cursor.execute("SELECT SEQ_NOM_PARTENERI.NEXTVAL FROM DUAL")
    new_id = cursor.fetchone()[0]
    
    # Clean name
    partner_name = (name or f"PARTENER {cui_clean}")[:100]
    partner_address = (address or "")[:200]
    
    cursor.execute("""
        INSERT INTO NOM_PARTENERI (ID_PART, NUME, COD_FISCAL, ADRESA, STERS, INACTIV)
        VALUES (:id_part, :nume, :cui, :adresa, 0, 0)
    """, id_part=new_id, nume=partner_name, cui=cui_clean, adresa=partner_address)
    
    print(f"  ➕ Partener nou creat: ID={new_id}, CUI={cui_clean}, Nume={partner_name}")
    return new_id


def check_tva_incasare(cursor, an: int, luna: int) -> bool:
    """Verifică dacă firma e plătitoare de TVA la încasare în perioada dată."""
    cursor.execute("""
        SELECT NVL(TVA_INCASARE, 0) FROM CALENDAR 
        WHERE AN = :an AND LUNA = :luna
    """, an=an, luna=luna)
    row = cursor.fetchone()
    return row[0] == 1 if row else False


def save_to_oracle(ocr_result: dict, do_commit: bool = False) -> dict:
    """Salvează nota contabilă în Oracle cu toate regulile."""
    conn = oracledb.connect(**ORACLE_CONFIG)
    cursor = conn.cursor()
    
    try:
        # Parse date
        date_str = ocr_result.get("receipt_date")
        if date_str:
            receipt_date = datetime.strptime(date_str[:10], "%Y-%m-%d").date()
        else:
            receipt_date = datetime.now().date()
        
        an, luna = receipt_date.year, receipt_date.month
        
        # 1. Get or create partner
        id_part = get_or_create_partner(
            cursor,
            ocr_result.get("cui"),
            ocr_result.get("partner_name"),
            ocr_result.get("address")
        )
        print(f"  Partner ID: {id_part}")
        
        # 2. Check TVA la încasare
        tva_incasare = check_tva_incasare(cursor, an, luna)
        cont_tva = "4428" if tva_incasare else "4426"
        print(f"  TVA la încasare: {'DA (4428)' if tva_incasare else 'NU (4426)'}")
        
        # 3. Determine payment type
        payment_methods = ocr_result.get("payment_methods") or []
        has_cash = any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods)
        has_card = any(pm.get("method", "").upper() == "CARD" for pm in payment_methods)
        
        # If no payment info, assume cash
        if not payment_methods:
            has_cash = True
        
        print(f"  Plată: {'NUMERAR' if has_cash else ''}{' + ' if has_cash and has_card else ''}{'CARD' if has_card else ''}")
        
        # 4. Init PACK_CONTAFIN
        cursor.callproc('PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL', 
                       [0, datetime.now(), an, luna, 0, 0, 0, 0])
        
        # 5. Get next COD
        cursor.execute(
            "SELECT NVL(MAX(COD), 0) + 1 FROM ACT WHERE AN = :an AND LUNA = :luna",
            an=an, luna=luna
        )
        cod = cursor.fetchone()[0]
        
        # 6. Calculate amounts
        total = float(ocr_result.get("amount") or 0)
        tva = float(ocr_result.get("tva_total") or 0)
        fara_tva = total - tva
        
        nract = ocr_result.get("receipt_number", "")
        nract = int(nract) if str(nract).isdigit() else 0
        
        cont_cheltuiala = get_cont(ocr_result.get("cui") or "")
        expl = f"OCR: {ocr_result.get('partner_name') or 'N/A'}"[:100]
        
        print(f"  COD: {cod}, Cont: {cont_cheltuiala}")
        print(f"  Total: {total}, Bază: {fara_tva}, TVA: {tva}")
        
        # 7. Process TVA entries from OCR (pot fi mai multe cote TVA)
        tva_entries = ocr_result.get("tva_entries") or []
        
        # 8. Build accounting lines
        lines = []
        
        # Calculate base for each TVA rate
        if tva_entries:
            # Process each TVA entry separately
            for tva_entry in tva_entries:
                tva_rate = tva_entry.get("percent") or 21
                tva_amount = float(tva_entry.get("amount") or 0)
                
                if tva_amount <= 0:
                    continue
                
                # Calculate base for this TVA rate
                base_amount = tva_amount / (tva_rate / 100)
                
                # Get ID_JTVA_COLOANA and TAXCODE based on TVA rate and TVA la încasare
                if tva_incasare:
                    jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
                else:
                    jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
                
                jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
                taxcode = taxcode_tvai if tva_incasare else taxcode_normal
                
                print(f"  TVA {tva_rate}%: baza={base_amount:.2f}, tva={tva_amount:.2f}, JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
                
                # Linia cheltuială pentru această cotă
                lines.append({
                    "scd": cont_cheltuiala, "scc": "401", 
                    "suma": base_amount, "expl": expl,
                    "id_partc": id_part, "id_partd": 0,
                    "id_jtva": jtva_baza,
                    "taxcode": taxcode
                })
                
                # Linia TVA pentru această cotă
                proc_tva = 1 + tva_rate / 100  # 1.21, 1.19, etc.
                lines.append({
                    "scd": cont_tva, "scc": "401",
                    "suma": tva_amount, "expl": f"TVA {tva_rate}% {expl}"[:100],
                    "id_partc": id_part, "id_partd": 0,
                    "proc_tva": proc_tva,
                    "id_jtva": jtva_tva,
                    "taxcode": taxcode
                })
        else:
            # Fallback: use total amounts if no tva_entries
            if fara_tva > 0:
                tva_rate = round(tva / fara_tva * 100) if fara_tva > 0 else 21
            else:
                tva_rate = 21
            
            if tva_incasare:
                jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
            else:
                jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
            
            jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
            taxcode = taxcode_tvai if tva_incasare else taxcode_normal
            
            print(f"  TVA {tva_rate}% (estimat): JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
            
            lines.append({
                "scd": cont_cheltuiala, "scc": "401", 
                "suma": fara_tva, "expl": expl,
                "id_partc": id_part, "id_partd": 0,
                "id_jtva": jtva_baza,
                "taxcode": taxcode
            })
            
            if tva > 0:
                proc_tva = 1 + tva_rate / 100
                lines.append({
                    "scd": cont_tva, "scc": "401",
                    "suma": tva, "expl": f"TVA {tva_rate}% {expl}"[:100],
                    "id_partc": id_part, "id_partd": 0,
                    "proc_tva": proc_tva,
                    "id_jtva": jtva_tva,
                    "taxcode": taxcode
                })
        
        # Linia plată din casă (DOAR dacă plată numerar)
        if has_cash and not has_card:
            lines.append({
                "scd": "401", "scc": "5311",
                "suma": total, "expl": f"Plata {expl}"[:100],
                "id_partc": 0, "id_partd": id_part,
                "id_jtva": None,  # Nu are JTVA pentru plată
                "taxcode": None
            })
        # Dacă plată CARD - nu se face nota 401=5311 (se face la extras bancar)
        
        # ID_FDOC = 17 pentru BON FISCAL
        id_fdoc = 17
        
        # 9. Insert lines
        for line in lines:
            proc_tva = line.get("proc_tva") or 0  # Default 0 for non-TVA lines
            id_jtva = line.get("id_jtva")  # Poate fi None pentru plăți
            taxcode = line.get("taxcode")  # Poate fi None pentru plăți
            cursor.execute("""
                INSERT INTO ACT_TEMP (
                    LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
                    EXPLICATIA, SCD, SCC, SUMA, PROC_TVA,
                    ID_PARTC, ID_PARTD, ID_FDOC, ID_JTVA_COLOANA, TAXCODE, ID_UTIL, DATAORA,
                    STERS, ID_SET, ID_SUCURSALA
                ) VALUES (
                    :luna, :an, :cod, TRUNC(SYSDATE), :dataact, :nract,
                    :expl, :scd, :scc, :suma, :proc_tva,
                    :id_partc, :id_partd, :id_fdoc, :id_jtva, :taxcode, 0, SYSDATE,
                    0, 0, 0
                )
            """, 
                luna=luna, an=an, cod=cod, dataact=receipt_date, nract=nract,
                expl=line["expl"], scd=line["scd"], scc=line["scc"], 
                suma=line["suma"], proc_tva=proc_tva,
                id_partc=line["id_partc"], id_partd=line["id_partd"], 
                id_fdoc=id_fdoc, id_jtva=id_jtva, taxcode=taxcode
            )
            jtva_info = f" [JTVA={id_jtva}]" if id_jtva else ""
            taxcode_info = f" [TAX={taxcode}]" if taxcode else ""
            print(f"    {line['scd']} = {line['scc']}: {line['suma']:.2f}{jtva_info}{taxcode_info}")
        
        # 9. Finalize
        mesaj = cursor.var(oracledb.STRING, 4000)
        cursor.callproc('PACK_CONTAFIN.FINALIZEAZA_SCRIERE_ACT_RUL',
                       [0, cod, 0, 0, 0, mesaj])
        
        if do_commit:
            conn.commit()
            return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": True, 
                    "id_part": id_part, "tva_incasare": tva_incasare}
        else:
            conn.rollback()
            return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": False,
                    "id_part": id_part, "tva_incasare": tva_incasare}
            
    except Exception as e:
        conn.rollback()
        import traceback
        return {"success": False, "error": str(e), "traceback": traceback.format_exc()}
    finally:
        cursor.close()
        conn.close()


def process_bon(file_path: Path, do_save: bool = False, company_id: int = COMPANY_ID,
                api_user: str = API_USER, api_pass: str = API_PASS):
    """Procesează un bon fiscal: OCR → SQLite → Oracle."""
    print("=" * 60)
    print(f"📄 Procesez: {file_path.name}")
    print("=" * 60)
    
    # 1. Login
    print("\n🔑 Login API...")
    client = APIClient(API_BASE)
    if not client.login(api_user, api_pass, SERVER_ID):
        print("❌ Login failed!")
        return None
    print("  ✅ OK")
    
    # 2. OCR
    print("\n🔍 OCR extract...")
    ocr_result = client.ocr_extract(file_path)
    if not ocr_result["success"]:
        print(f"  ❌ {ocr_result['error']}")
        return None
    
    ocr = ocr_result["result"]
    print(f"  ✅ OK ({ocr_result.get('time_ms', '?')}ms)")
    print(f"     CUI: {ocr.get('cui')}")
    print(f"     Partner: {ocr.get('partner_name')}")
    print(f"     Data: {ocr.get('receipt_date')}")
    print(f"     Total: {ocr.get('amount')} RON")
    print(f"     TVA: {ocr.get('tva_total')} RON")
    
    # Show payment methods
    payment_methods = ocr.get("payment_methods") or []
    if payment_methods:
        pm_str = ", ".join(f"{pm.get('method')}: {pm.get('amount')}" for pm in payment_methods)
        print(f"     Plăți: {pm_str}")
    
    # Show TVA breakdown
    tva_entries = ocr.get("tva_entries") or []
    if tva_entries:
        tva_str = ", ".join(f"{t.get('code')}({t.get('percent')}%): {t.get('amount')}" for t in tva_entries)
        print(f"     TVA detaliat: {tva_str}")
    
    # 3. SQLite (via API) - cu TOATE datele
    print("\n💾 Save SQLite (via API)...")
    sqlite_result = client.create_receipt(ocr, company_id)
    if not sqlite_result["success"]:
        print(f"  ❌ {sqlite_result['error']}")
        return None
    
    receipt = sqlite_result["receipt"]
    print(f"  ✅ Receipt ID: {receipt.get('id')}")
    print(f"     Payment mode: {receipt.get('payment_mode')}")
    
    # 4. Oracle (direct)
    mode = "SAVE" if do_save else "DRY RUN"
    print(f"\n🗄️  Save Oracle ({mode})...")
    oracle_result = save_to_oracle(ocr, do_commit=do_save)
    
    if oracle_result["success"]:
        if oracle_result["saved"]:
            print(f"  ✅ SALVAT: COD={oracle_result['cod']}, {oracle_result['luna']:02d}/{oracle_result['an']}")
        else:
            print(f"  ⚠️  DRY RUN: ar fi COD={oracle_result['cod']}")
    else:
        print(f"  ❌ {oracle_result.get('error')}")
        if oracle_result.get("traceback"):
            print(oracle_result["traceback"])
    
    print("\n" + "=" * 60)
    return {
        "ocr": ocr,
        "sqlite_receipt_id": receipt.get("id"),
        "oracle": oracle_result
    }


def main():
    parser = argparse.ArgumentParser(description="Procesare bon fiscal: OCR → SQLite → Oracle")
    parser.add_argument("file", help="Path către PDF sau imagine")
    parser.add_argument("--save", action="store_true", help="Salvează efectiv în Oracle")
    parser.add_argument("--company", type=int, default=COMPANY_ID, help="Company ID")
    parser.add_argument("--user", default=API_USER, help="API username")
    parser.add_argument("--password", default=API_PASS, help="API password")
    
    args = parser.parse_args()
    
    file_path = Path(args.file)
    if not file_path.exists():
        print(f"❌ File not found: {file_path}")
        sys.exit(1)
    
    result = process_bon(file_path, do_save=args.save, company_id=args.company,
                        api_user=args.user, api_pass=args.password)
    
    if result:
        print("\n✅ Done!")
    else:
        print("\n❌ Failed!")
        sys.exit(1)


if __name__ == "__main__":
    main()
