"""RBS REGAL — Godowns (Warehouses) module.

Multi-warehouse stock management. Each godown can hold stock-per-item.
Stock transfers move quantity between godowns and create audit entries.

Collections:
  godowns         : {company_id, name, address, manager_name, phone, type, ...}
  godown_stock    : {company_id, godown_id, item_id, qty, updated_at}
  stock_transfers : {company_id, from_godown_id, to_godown_id, transfer_no, date,
                     items: [{item_id, qty}], notes, status, created_at, created_by}
"""
from datetime import datetime, timezone
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, Body, Query
from bson import ObjectId
from auth import get_current_user, require_admin

router = APIRouter(prefix="/api/godowns", tags=["godowns"])


def _now():
    return datetime.now(timezone.utc).isoformat()


def _ser(d):
    if not d: return d
    out = dict(d)
    _id = out.pop("_id", None)
    if _id is not None:
        out["id"] = str(_id)
    return out


def _oid(s):
    try: return ObjectId(s)
    except Exception: raise HTTPException(400, "Invalid id")


# ---------- Godown CRUD ----------
@router.get("")
async def list_godowns(
    request: Request,
    company_id: Optional[str] = None,
    user=Depends(get_current_user),
):
    db = request.app.state.db
    q = {"company_id": company_id} if company_id else {}
    docs = await db.godowns.find(q).sort("name", 1).to_list(200)
    return [_ser(d) for d in docs]


@router.post("")
async def create_godown(
    payload: dict = Body(...),
    company_id: str = Query(...),
    request: Request = None,
    user=Depends(require_admin),
):
    db = request.app.state.db
    name = (payload.get("name") or "").strip()
    if not name:
        raise HTTPException(400, "Godown name is required")
    if await db.godowns.find_one({"company_id": company_id, "name": name}):
        raise HTTPException(409, f"A godown named '{name}' already exists")
    doc = {
        "company_id": company_id,
        "name": name,
        "address": (payload.get("address") or "").strip(),
        "manager_name": (payload.get("manager_name") or "").strip(),
        "phone": (payload.get("phone") or "").strip(),
        "type": payload.get("type", "warehouse"),  # warehouse / shop / yard / other
        "is_default": bool(payload.get("is_default", False)),
        "created_at": _now(),
    }
    if doc["is_default"]:
        # Clear other defaults
        await db.godowns.update_many({"company_id": company_id, "is_default": True}, {"$set": {"is_default": False}})
    res = await db.godowns.insert_one(doc)
    return _ser({**doc, "_id": res.inserted_id})


@router.put("/{gid}")
async def update_godown(
    gid: str,
    payload: dict = Body(...),
    request: Request = None,
    user=Depends(require_admin),
):
    db = request.app.state.db
    existing = await db.godowns.find_one({"_id": _oid(gid)})
    if not existing:
        raise HTTPException(404, "Godown not found")
    allowed = {"name", "address", "manager_name", "phone", "type", "is_default"}
    patch = {k: v for k, v in payload.items() if k in allowed}
    if "name" in patch:
        clash = await db.godowns.find_one({
            "company_id": existing["company_id"],
            "name": patch["name"],
            "_id": {"$ne": _oid(gid)},
        })
        if clash:
            raise HTTPException(409, f"A godown named '{patch['name']}' already exists")
    if patch.get("is_default"):
        await db.godowns.update_many(
            {"company_id": existing["company_id"], "is_default": True, "_id": {"$ne": _oid(gid)}},
            {"$set": {"is_default": False}},
        )
    patch["updated_at"] = _now()
    await db.godowns.update_one({"_id": _oid(gid)}, {"$set": patch})
    out = await db.godowns.find_one({"_id": _oid(gid)})
    return _ser(out)


@router.delete("/{gid}")
async def delete_godown(
    gid: str,
    request: Request,
    user=Depends(require_admin),
):
    db = request.app.state.db
    existing = await db.godowns.find_one({"_id": _oid(gid)})
    if not existing:
        raise HTTPException(404, "Godown not found")
    # Block deletion if stock exists
    has_stock = await db.godown_stock.find_one({"godown_id": gid, "qty": {"$gt": 0}})
    if has_stock:
        raise HTTPException(400, "Cannot delete: this godown holds stock. Transfer it out first.")
    has_xfer = await db.stock_transfers.find_one({"$or": [{"from_godown_id": gid}, {"to_godown_id": gid}]})
    if has_xfer:
        raise HTTPException(400, "Cannot delete: this godown has historical transfers. Mark inactive instead.")
    await db.godowns.delete_one({"_id": _oid(gid)})
    await db.godown_stock.delete_many({"godown_id": gid})
    return {"ok": True}


# ---------- Godown Stock ----------
@router.get("/{gid}/stock")
async def godown_stock(
    gid: str,
    request: Request,
    user=Depends(get_current_user),
):
    db = request.app.state.db
    rows = await db.godown_stock.find({"godown_id": gid}).to_list(50000)
    # Join with item names
    item_ids = [r["item_id"] for r in rows]
    items = {}
    if item_ids:
        oids = [_oid(i) for i in item_ids]
        async for it in db.items.find({"_id": {"$in": oids}}):
            items[str(it["_id"])] = it
    out = []
    for r in rows:
        it = items.get(r["item_id"], {})
        out.append({
            "item_id": r["item_id"],
            "item_name": it.get("name"),
            "item_code": it.get("code"),
            "unit": it.get("unit"),
            "qty": r.get("qty", 0),
            "updated_at": r.get("updated_at"),
        })
    out.sort(key=lambda x: (x["item_name"] or ""))
    return out


# ---------- Stock Transfers ----------
async def _next_transfer_no(db, company_id: str) -> str:
    today = datetime.now(timezone.utc).strftime("%Y%m%d")
    count = await db.stock_transfers.count_documents({"company_id": company_id})
    return f"ST-{today}-{count + 1:04d}"


@router.post("/transfers")
async def create_transfer(
    payload: dict = Body(...),
    company_id: str = Query(...),
    request: Request = None,
    user=Depends(get_current_user),
):
    db = request.app.state.db
    frm = payload.get("from_godown_id")
    to = payload.get("to_godown_id")
    items = payload.get("items") or []
    if not frm or not to:
        raise HTTPException(400, "from_godown_id and to_godown_id required")
    if frm == to:
        raise HTTPException(400, "Source and destination must differ")
    if not items:
        raise HTTPException(400, "At least one item required")
    # Validate godowns exist & belong to company
    g_from = await db.godowns.find_one({"_id": _oid(frm), "company_id": company_id})
    g_to = await db.godowns.find_one({"_id": _oid(to), "company_id": company_id})
    if not g_from or not g_to:
        raise HTTPException(400, "Invalid godown")

    # Validate stock availability
    errors = []
    for it in items:
        iid = it.get("item_id")
        qty = float(it.get("qty") or 0)
        if qty <= 0: continue
        src = await db.godown_stock.find_one({"godown_id": frm, "item_id": iid})
        avail = (src or {}).get("qty", 0)
        if avail < qty:
            errors.append(f"Item {iid}: only {avail} available, need {qty}")
    if errors:
        raise HTTPException(400, "; ".join(errors))

    # Apply transfer atomically
    now = _now()
    transfer_no = await _next_transfer_no(db, company_id)
    doc = {
        "company_id": company_id,
        "transfer_no": transfer_no,
        "from_godown_id": frm,
        "to_godown_id": to,
        "from_godown_name": g_from["name"],
        "to_godown_name": g_to["name"],
        "date": payload.get("date") or now,
        "items": items,
        "notes": (payload.get("notes") or "").strip(),
        "status": "completed",
        "created_at": now,
        "created_by": user["email"],
    }
    res = await db.stock_transfers.insert_one(doc)

    # Adjust stock
    for it in items:
        iid = it.get("item_id")
        qty = float(it.get("qty") or 0)
        if qty <= 0: continue
        # Source decrement
        await db.godown_stock.update_one(
            {"godown_id": frm, "item_id": iid},
            {"$inc": {"qty": -qty}, "$set": {"updated_at": now, "company_id": company_id}},
            upsert=True,
        )
        # Destination increment
        await db.godown_stock.update_one(
            {"godown_id": to, "item_id": iid},
            {"$inc": {"qty": qty}, "$set": {"updated_at": now, "company_id": company_id}},
            upsert=True,
        )

    await db.activity_logs.insert_one({
        "user_id": user["id"], "user_email": user["email"], "action": "transfer",
        "entity": "stock", "meta": {"transfer_no": transfer_no, "from": g_from["name"], "to": g_to["name"], "items": len(items)},
        "timestamp": now,
    })

    return _ser({**doc, "_id": res.inserted_id})


@router.get("/transfers")
async def list_transfers(
    request: Request,
    company_id: Optional[str] = None,
    limit: int = 100,
    user=Depends(get_current_user),
):
    db = request.app.state.db
    q = {"company_id": company_id} if company_id else {}
    docs = await db.stock_transfers.find(q).sort("date", -1).to_list(min(limit, 500))
    return [_ser(d) for d in docs]


@router.get("/summary")
async def summary(
    request: Request,
    company_id: str = Query(...),
    user=Depends(get_current_user),
):
    """Returns per-godown total items + total qty value."""
    db = request.app.state.db
    godowns = await db.godowns.find({"company_id": company_id}).to_list(200)
    items = await db.items.find({"company_id": company_id}).to_list(50000)
    item_map = {str(it["_id"]): it for it in items}
    out = []
    for g in godowns:
        gid = str(g["_id"])
        stocks = await db.godown_stock.find({"godown_id": gid}).to_list(50000)
        total_qty = sum((s.get("qty") or 0) for s in stocks if (s.get("qty") or 0) > 0)
        total_value = 0
        for s in stocks:
            it = item_map.get(s.get("item_id"))
            if it: total_value += (s.get("qty") or 0) * (it.get("purchase_price") or 0)
        unique_items = sum(1 for s in stocks if (s.get("qty") or 0) > 0)
        out.append({
            **_ser(g),
            "total_qty": total_qty,
            "total_value": total_value,
            "unique_items": unique_items,
        })
    return out
