from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.orm import Session
from typing import List, Optional
from pydantic import BaseModel
from io import BytesIO
from datetime import date
import re
import difflib
import datetime as dt
try:
    import pdfplumber  # type: ignore
except Exception:  # pragma: no cover
    pdfplumber = None

from app.schemas.delivery import DeliveryCreate, DeliveryOut
from app.models.delivery import Delivery
from app.models.product import Product
from app.core.database import SessionLocal
from app.core.auth import get_current_user, requires_role
from app.models.user import User
router = APIRouter(prefix="/deliveries", tags=["deliveries"])

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/", response_model=DeliveryOut, dependencies=[Depends(requires_role("manager", "admin"))])
def create_delivery(delivery: DeliveryCreate, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    # Session.get statt .query().get (SQLAlchemy 2.x)
    product = db.get(Product, delivery.product_id)
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")
    if delivery.amount < 1:
        raise HTTPException(status_code=400, detail="Amount must be at least 1")

    # Pydantic v2: model_dump()
    db_delivery = Delivery(**delivery.model_dump())
    db.add(db_delivery)
    db.commit()
    db.refresh(db_delivery)
    return db_delivery

@router.get("/", response_model=list[DeliveryOut], dependencies=[Depends(requires_role("manager", "admin"))])
def list_deliveries(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    return db.query(Delivery).all()

@router.get("/{delivery_id}", response_model=DeliveryOut, dependencies=[Depends(requires_role("manager", "admin"))])
def get_delivery(delivery_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    delivery = db.get(Delivery, delivery_id)
    if not delivery:
        raise HTTPException(status_code=404, detail="Delivery not found")
    return delivery

@router.delete("/{delivery_id}", status_code=204, dependencies=[Depends(requires_role("manager", "admin"))])
def delete_delivery(delivery_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    delivery = db.get(Delivery, delivery_id)
    if not delivery:
        raise HTTPException(status_code=404, detail="Delivery not found")
    db.delete(delivery)
    db.commit()

# ---- Draft-Schemata für den PDF-Import (Antwort an das Frontend) -----------------------
class DeliveryItemDraft(BaseModel):
    product_id: Optional[int] = None
    product_hint: Optional[str] = None  # originaler Text aus der PDF
    quantity_units: int                 # Anzahl (Einheiten/KA)
    unit_cost_cents: int                # E-Preis je Einheit in Cent

class DeliveryDraft(BaseModel):
    supplier: Optional[str] = None
    date: Optional[str] = None          # ISO yyyy-mm-dd
    invoice_no: Optional[str] = None
    note: Optional[str] = None
    deposit_return_cents: int = 0       # Netto-Pfand (Cent, positiv)
    items: List[DeliveryItemDraft] = []

# ---- Hilfsfunktionen -------------------------------------------------------------------
def _parse_decimal_de(s: str) -> float:
    """'1.234,56' -> 1234.56"""
    t = s.strip().replace(".", "").replace("’", "").replace(",", ".")
    try:
        return float(t)
    except ValueError:
        return 0.0

def _to_cents(v: float) -> int:
    return int(round(v * 100))

def _date_de_to_iso(s: str) -> Optional[str]:
    try:
        d = dt.datetime.strptime(s.strip(), "%d.%m.%Y").date()
        return d.isoformat()
    except Exception:
        return None

_pack_pat = re.compile(r"(\d+)\s*/\s*(\d+[.,]?\d*)")  # z.B. 24/0,33 oder 12/1,0

def _extract_pack_size(text: str) -> Optional[int]:
    m = _pack_pat.search(text)
    if not m:
        return None
    try:
        return int(m.group(1))
    except Exception:
        return None

def _norm(s: str) -> str:
    return re.sub(r"[^0-9a-z]+", " ", s.lower()).strip()

def _best_product_match(desc: str, products: List[Product]) -> Optional[int]:
    """Einfache Fuzzy-Suche: Textähnlichkeit + Bonus bei passender Packgröße."""
    if not products:
        return None
    cand_name = _norm(desc)
    want_pack = _extract_pack_size(desc)
    best_id, best_score = None, 0.0
    for p in products:
        name = getattr(p, "name", "") or ""
        score = difflib.SequenceMatcher(a=cand_name, b=_norm(name)).ratio()
        pack = getattr(p, "pack_size", None)
        if want_pack and pack and int(pack) == int(want_pack):
            score += 0.08  # leichter Bonus
        if score > best_score:
            best_score, best_id = score, int(getattr(p, "id"))
    return best_id if best_score >= 0.75 else None

# ---- Kern: PDF parsen (ein Supplier, textbasiert) --------------------------------------
def _parse_invoice_pdf(data: bytes, all_products: List[Product]) -> DeliveryDraft:
    if pdfplumber is None:
        raise HTTPException(
            status_code=501,
            detail="pdfplumber ist nicht installiert. Bitte `pip install pdfplumber` ausführen."
        )

    text_pages: List[str] = []
    with pdfplumber.open(BytesIO(data)) as pdf:
        for page in pdf.pages:
            # Text mit Zeilenumbrüchen; für dieses Layout reicht Text-Parsing
            text_pages.append(page.extract_text() or "")

    full = "\n".join(text_pages)

    # Header-Felder
    inv = None
    m = re.search(r"Rechnungs[-\s]?Nr\.?\s*:\s*([A-Za-z0-9\-\/]+)", full, re.I)
    if m:
        inv = m.group(1).strip()

    date_iso = None
    m = re.search(r"Datum\s*:\s*(\d{2}\.\d{2}\.\d{4})", full, re.I)
    if m:
        date_iso = _date_de_to_iso(m.group(1))

    deposit_cents = 0

    # 1) Bevorzugt die Steuerzeile netto Summe Pfand
    m = re.search(r"(?mi)^\s*summe\s+pfand\b.*?([0-9\.,]+)\s*$", full)
    if not m:
        # 2) Fallback
        m = re.search(r"(?mi)^\s*pfand\b.*?eur\s*([0-9\.,]+)\s*$", full)

    if m:
        deposit_cents = _to_cents(_parse_decimal_de(m.group(1)))

    # Positionsblock: Zeilen zwischen Kopf "Art-Nr" und "Zwischensumme Warenwert"
    items: List[DeliveryItemDraft] = []
    block = []
    in_block = False
    for line in full.splitlines():
        if not in_block and re.search(r"\bArt[-\s]?Nr\b", line):
            in_block = True
            continue
        if in_block and re.search(r"Zwischensumme\s+Warenwert", line):
            break
        if in_block:
            if line.strip():
                block.append(line)

    # Zeilen parsen: "<artnr> <bezeichnung…> <anzahl> <ME> <E-Preis> <G-Preis>"
    row_re = re.compile(
        r"^\s*\d+\s+(?P<desc>.+?)\s+(?P<qty>\d+)\s+(?P<me>[A-Za-z]+)\s+(?P<eprice>\d+,\d{2})\s+(?P<gprice>\d+,\d{2})\s*$"
    )
    for ln in block:
        m = row_re.match(ln)
        if not m:
            continue
        desc = m.group("desc").strip()
        qty = int(m.group("qty"))
        me = m.group("me").upper()
        eprice = _to_cents(_parse_decimal_de(m.group("eprice")))
        # Nur ME = KA (Kästen/Einheit) übernehmen – andere ignorieren
        if me not in {"KA", "KASTEN", "EINHEIT"}:
            # zur Not trotzdem übernehmen
            pass
        prod_id = _best_product_match(desc, all_products)
        items.append(
            DeliveryItemDraft(
                product_id=prod_id,
                product_hint=(None if prod_id else desc),
                quantity_units=qty,
                unit_cost_cents=eprice,
            )
        )

    return DeliveryDraft(
        supplier=None,            # optional – kann bei Bedarf aus Kopf extrahiert werden
        date=date_iso,
        invoice_no=inv,
        note=None,
        deposit_return_cents=abs(deposit_cents),
        items=items or [],
    )

# ---- Endpoint: PDF hochladen & Draft liefern -------------------------------------------
@router.post("/invoice/import", response_model=DeliveryDraft, dependencies=[Depends(requires_role("manager", "admin"))])
def import_invoice_pdf(
    file: UploadFile = File(...),   # ← fix
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    if not file.filename.lower().endswith(".pdf"):
        raise HTTPException(status_code=400, detail="Bitte eine PDF-Datei hochladen.")

    data = file.file.read()
    if not data:
        raise HTTPException(status_code=400, detail="Leere Datei.")

    # Produkte für Matching laden
    products = db.query(Product).all()

    try:
        draft = _parse_invoice_pdf(data, products)
    except HTTPException:
        raise
    except Exception as e:  # robustes Fehlerhandling
        raise HTTPException(status_code=422, detail=f"PDF konnte nicht geparst werden: {e}")

    # Optional: minimale Plausibilitätsprüfung
    if not draft.items:
        # Kein harter Fehler – Frontend kann manuell ergänzen
        draft.note = (draft.note or "")
    return draft

# ---- Bulk-Schema ----
class DeliveryItemIn(BaseModel):
    product_id: int
    quantity_units: int
    unit_cost_cents: int
    units: int | None = None  # optional: Stückzahl vom Client

class DeliveryBulkIn(BaseModel):
    supplier: str
    date: date
    invoice_no: str | None = None
    note: str | None = None
    deposit_return_cents: int = 0
    items: list[DeliveryItemIn]

@router.post("/bulk")
def create_delivery_bulk(body: DeliveryBulkIn, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    requires_role("manager", "admin")(user)
    created_ids: list[int] = []
    deposit = int(body.deposit_return_cents or 0)
    for idx, it in enumerate(body.items):
        prod = db.get(Product, it.product_id) or HTTPException(status_code=400, detail=f"Unknown product_id {it.product_id}")
        pack = int(getattr(prod, "pack_size") or 1)
        amount_pieces = int(it.units) if it.units is not None else int(it.quantity_units) * pack
        row = Delivery(
            product_id=it.product_id,
            amount=amount_pieces,                     # Stück gesamt
            price_cents=int(it.unit_cost_cents),      # Preis pro Stück
            delivered_at=body.date,
            supplier=body.supplier,
            invoice_number=body.invoice_no,
            note=body.note,
            deposit_return_cents=(deposit if idx == 0 else 0),  # <- hier
        )
        db.add(row); db.flush(); created_ids.append(row.id)
    db.commit()
    return {"created": created_ids}

