from __future__ import annotations
import uuid
from datetime import datetime, date
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.database import get_db
from app.models.purchase import PurchaseRequest, PurchaseOrder, PurchaseItem, GoodsReceiptNote, GRNItem
from app.models.supplier import Supplier
from app.models.inventory import InventoryItem, InventoryTransaction
from app.models.finance import JournalEntry
from app.services.auth import get_current_user
from app.services.accounting import AccountingError
from app.services.transaction_postings import post_grn, grn_posting_entry

router = APIRouter(prefix="/purchases", tags=["purchases"])


class PurchaseRequestCreate(BaseModel):
    requested_by: str
    department: Optional[str] = None
    notes: Optional[str] = None

class PurchaseRequestUpdate(BaseModel):
    status: Optional[str] = None
    notes: Optional[str] = None

class PurchaseRequestOut(BaseModel):
    id: str
    request_number: str
    requested_by: str
    department: Optional[str] = None
    status: str
    notes: Optional[str] = None
    created_at: datetime
    model_config = {"from_attributes": True}

class PurchaseItemCreate(BaseModel):
    inventory_id: Optional[str] = None
    description: str
    quantity: int = 1
    unit_price: float = 0.0

class PurchaseItemOut(BaseModel):
    id: str
    order_id: str
    inventory_id: Optional[str] = None
    description: str
    quantity: int
    unit_price: float
    total: float
    model_config = {"from_attributes": True}

class PurchaseOrderCreate(BaseModel):
    supplier_id: str
    order_date: Optional[date] = None
    expected_date: Optional[date] = None
    notes: Optional[str] = None
    items: List[PurchaseItemCreate] = []

class PurchaseOrderUpdate(BaseModel):
    supplier_id: Optional[str] = None
    order_date: Optional[date] = None
    expected_date: Optional[date] = None
    status: Optional[str] = None
    notes: Optional[str] = None

class PurchaseOrderOut(BaseModel):
    id: str
    po_number: str
    supplier_id: str
    order_date: date
    expected_date: Optional[date] = None
    total: float
    status: str
    notes: Optional[str] = None
    created_by: str
    created_at: datetime
    updated_at: datetime
    model_config = {"from_attributes": True}

class GRNItemCreate(BaseModel):
    purchase_order_item_id: Optional[str] = None
    received_quantity: float = 0

class GRNCreate(BaseModel):
    purchase_order_id: Optional[str] = None
    supplier_id: Optional[str] = None
    received_date: Optional[date] = None
    branch_id: Optional[str] = None
    items: List[GRNItemCreate] = []
    notes: Optional[str] = None

class GRNOut(BaseModel):
    id: str
    grn_number: str
    purchase_order_id: Optional[str] = None
    supplier_id: str
    received_date: date
    total: float
    status: str
    notes: Optional[str] = None
    received_by: str
    created_at: datetime
    model_config = {"from_attributes": True}


def _generate_request_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(PurchaseRequest).filter(
        PurchaseRequest.request_number.like(f"PR-{year}-%")
    ).order_by(PurchaseRequest.request_number.desc()).first()
    if last:
        seq = int(last.request_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"PR-{year}-{seq:06d}"


def _generate_po_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(PurchaseOrder).filter(
        PurchaseOrder.po_number.like(f"PO-{year}-%")
    ).order_by(PurchaseOrder.po_number.desc()).first()
    if last:
        seq = int(last.po_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"PO-{year}-{seq:06d}"


def _generate_grn_number(db: Session) -> str:
    year = datetime.now().year
    last = db.query(GoodsReceiptNote).filter(
        GoodsReceiptNote.grn_number.like(f"GRN-{year}-%")
    ).order_by(GoodsReceiptNote.grn_number.desc()).first()
    if last:
        seq = int(last.grn_number.split("-")[-1]) + 1
    else:
        seq = 1
    return f"GRN-{year}-{seq:06d}"


@router.get("/requests", response_model=list[PurchaseRequestOut])
def list_purchase_requests(
    status: Optional[str] = None,
    db: Session = Depends(get_db),
    _=Depends(get_current_user),
):
    q = db.query(PurchaseRequest)
    if status:
        q = q.filter(PurchaseRequest.status == status)
    return q.order_by(PurchaseRequest.created_at.desc()).all()


@router.post("/requests", response_model=PurchaseRequestOut, status_code=201)
def create_purchase_request(data: PurchaseRequestCreate, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = PurchaseRequest(**data.model_dump(), request_number=_generate_request_number(db))
    db.add(obj)
    db.commit()
    db.refresh(obj)
    return obj


@router.get("/requests/{request_id}", response_model=PurchaseRequestOut)
def get_purchase_request(request_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(PurchaseRequest).filter(PurchaseRequest.id == request_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Purchase request not found")
    return obj


@router.put("/requests/{request_id}", response_model=PurchaseRequestOut)
def update_purchase_request(request_id: str, data: PurchaseRequestUpdate, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(PurchaseRequest).filter(PurchaseRequest.id == request_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Purchase request not found")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.delete("/requests/{request_id}")
def delete_purchase_request(request_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(PurchaseRequest).filter(PurchaseRequest.id == request_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Purchase request not found")
    db.delete(obj)
    db.commit()
    return {"detail": "Purchase request deleted"}


@router.get("/orders", response_model=list[PurchaseOrderOut])
def list_purchase_orders(
    supplier_id: Optional[str] = None,
    status: Optional[str] = None,
    db: Session = Depends(get_db),
    _=Depends(get_current_user),
):
    q = db.query(PurchaseOrder)
    if supplier_id:
        q = q.filter(PurchaseOrder.supplier_id == supplier_id)
    if status:
        q = q.filter(PurchaseOrder.status == status)
    return q.order_by(PurchaseOrder.created_at.desc()).all()


@router.post("/orders", response_model=PurchaseOrderOut, status_code=201)
def create_purchase_order(data: PurchaseOrderCreate, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    supplier = db.query(Supplier).filter(Supplier.id == data.supplier_id).first()
    if not supplier:
        raise HTTPException(status_code=404, detail="Supplier not found")

    order = PurchaseOrder(
        po_number=_generate_po_number(db),
        supplier_id=data.supplier_id,
        order_date=data.order_date or date.today(),
        expected_date=data.expected_date,
        notes=data.notes,
        created_by=current_user.id,
    )
    db.add(order)
    db.flush()

    total = 0.0
    for item_data in data.items:
        item_total = item_data.quantity * item_data.unit_price
        total += item_total
        item = PurchaseItem(
            order_id=order.id,
            inventory_id=item_data.inventory_id,
            description=item_data.description,
            quantity=item_data.quantity,
            unit_price=item_data.unit_price,
            total=item_total,
        )
        db.add(item)

    order.total = total
    db.commit()
    db.refresh(order)
    return order


@router.get("/orders/{order_id}", response_model=PurchaseOrderOut)
def get_purchase_order(order_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(PurchaseOrder).filter(PurchaseOrder.id == order_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Purchase order not found")
    return obj


@router.get("/orders/{order_id}/items", response_model=list[PurchaseItemOut])
def get_purchase_order_items(order_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    return db.query(PurchaseItem).filter(PurchaseItem.order_id == order_id).all()


@router.put("/orders/{order_id}", response_model=PurchaseOrderOut)
def update_purchase_order(order_id: str, data: PurchaseOrderUpdate, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(PurchaseOrder).filter(PurchaseOrder.id == order_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Purchase order not found")
    for k, v in data.model_dump(exclude_unset=True).items():
        setattr(obj, k, v)
    db.commit()
    db.refresh(obj)
    return obj


@router.delete("/orders/{order_id}")
def delete_purchase_order(order_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(PurchaseOrder).filter(PurchaseOrder.id == order_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="Purchase order not found")
    db.delete(obj)
    db.commit()
    return {"detail": "Purchase order deleted"}


@router.get("/grn", response_model=list[GRNOut])
def list_grn(
    supplier_id: Optional[str] = None,
    db: Session = Depends(get_db),
    _=Depends(get_current_user),
):
    q = db.query(GoodsReceiptNote)
    if supplier_id:
        q = q.filter(GoodsReceiptNote.supplier_id == supplier_id)
    return q.order_by(GoodsReceiptNote.created_at.desc()).all()


@router.post("/grn", response_model=GRNOut, status_code=201)
def create_grn(data: GRNCreate, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    po = None
    if data.purchase_order_id:
        po = db.query(PurchaseOrder).filter(PurchaseOrder.id == data.purchase_order_id).first()
        if not po:
            raise HTTPException(status_code=404, detail="Purchase order not found")

    supplier_id = data.supplier_id or (po.supplier_id if po else None)
    if not supplier_id:
        raise HTTPException(status_code=400, detail="supplier_id is required")

    supplier = db.query(Supplier).filter(Supplier.id == supplier_id).first()
    if not supplier:
        raise HTTPException(status_code=404, detail="Supplier not found")

    grn = GoodsReceiptNote(
        grn_number=_generate_grn_number(db),
        purchase_order_id=data.purchase_order_id,
        supplier_id=supplier_id,
        received_date=data.received_date or date.today(),
        total=0.0,
        notes=data.notes,
        branch_id=data.branch_id or current_user.branch_id,
        received_by=current_user.id,
    )
    db.add(grn)
    db.flush()

    total = 0.0
    if data.items:
        for item_data in data.items:
            po_item = None
            if item_data.purchase_order_item_id:
                po_item = db.query(PurchaseItem).filter(
                    PurchaseItem.id == item_data.purchase_order_item_id).first()
            if po_item:
                qty = int(item_data.received_quantity)
                line_total = po_item.unit_price * qty
                total += line_total
                db.add(GRNItem(
                    grn_id=grn.id,
                    po_item_id=po_item.id,
                    inventory_id=po_item.inventory_id,
                    description=po_item.description,
                    quantity=qty,
                    unit_price=po_item.unit_price,
                    total=line_total,
                ))
    elif po:
        for po_item in po.items:
            qty = po_item.quantity
            line_total = po_item.unit_price * qty
            total += line_total
            db.add(GRNItem(
                grn_id=grn.id,
                po_item_id=po_item.id,
                inventory_id=po_item.inventory_id,
                description=po_item.description,
                quantity=qty,
                unit_price=po_item.unit_price,
                total=line_total,
            ))

    grn.total = total
    posted = False
    db.flush()
    if db.query(GRNItem).filter(GRNItem.grn_id == grn.id).first() is not None:
        if po:
            po.status = "received"
        try:
            post_grn(db, grn.id, current_user.id)
            posted = True
        except AccountingError as exc:
            db.rollback()
            raise HTTPException(status_code=400, detail=str(exc))

    db.commit()
    db.refresh(grn)
    return grn


@router.post("/grn/{grn_id}/post")
def post_grn_entry(grn_id: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
    grn = db.query(GoodsReceiptNote).filter(GoodsReceiptNote.id == grn_id).first()
    if not grn:
        raise HTTPException(status_code=404, detail="GRN not found")
    try:
        entry = post_grn(db, grn.id, current_user.id)
    except AccountingError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    db.commit()
    return {"grn_id": grn.id, "grn_number": grn.grn_number, "posted": True,
            "journal_entry_id": entry.id, "entry_number": entry.entry_number}


@router.get("/grn/{grn_id}/posting")
def get_grn_posting_status(grn_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    grn = db.query(GoodsReceiptNote).filter(GoodsReceiptNote.id == grn_id).first()
    if not grn:
        raise HTTPException(status_code=404, detail="GRN not found")
    entry = grn_posting_entry(db, grn)
    return {
        "grn_id": grn.id,
        "posted": bool(entry),
        "status": entry.status if entry else None,
        "journal_entry_id": entry.id if entry else None,
        "entry_number": entry.entry_number if entry else None,
    }


@router.get("/grn/posting-batch")
def get_grn_posting_batch(ids: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    id_list = [i for i in ids.split(",") if i][:200]
    entries = db.query(JournalEntry).filter(
        JournalEntry.source == "purchase",
        JournalEntry.reference_type == "grn",
        JournalEntry.reference_id.in_(id_list),
    ).all()
    by_id = {e.reference_id: e for e in entries}
    return {
        grn_id: {
            "posted": bool(by_id.get(grn_id)),
            "status": by_id[grn_id].status if by_id.get(grn_id) else None,
            "journal_entry_id": by_id[grn_id].id if by_id.get(grn_id) else None,
            "entry_number": by_id[grn_id].entry_number if by_id.get(grn_id) else None,
        }
        for grn_id in id_list
    }


@router.get("/grn/{grn_id}", response_model=GRNOut)
def get_grn(grn_id: str, db: Session = Depends(get_db), _=Depends(get_current_user)):
    obj = db.query(GoodsReceiptNote).filter(GoodsReceiptNote.id == grn_id).first()
    if not obj:
        raise HTTPException(status_code=404, detail="GRN not found")
    return obj
