import uuid
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text, update

from app.core.database import get_db
from app.core.security import decode_token
from app.models.rm_models import RmPurchaseOrder, RmPoDetail, RmReceivingLog, GrnDetail, PoStatusMaster, RmMaster, MonthlyProcurementPlan
from app.models.vendor_portal import VendorPortalLog, RejectionLog, ASNLog, ASNDetail, POTrackingLog, NCRLog
from app.modules.vendor_portal.schemas import VendorLoginRequest, VendorLoginResponse, POListResponse, ScorecardResponse, PODetailResponse, ASNSubmitRequest, ASNResponse, ASNListResponse, VendorMonthlyPlanResponse, VendorMonthlyPlanLineResponse
from app.modules.vendor_portal.service import authenticate_vendor, logout_vendor, log_vendor_action

router = APIRouter()

async def get_current_vendor(request: Request, db: AsyncSession = Depends(get_db)) -> dict:
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Missing or invalid token")
    
    token = auth_header.split(" ")[1]
    payload = decode_token(token)
    
    if payload.get("sub_type") != "vendor":
        raise HTTPException(status_code=403, detail="Not authorized as a vendor")
        
    session_id = payload.get("session_id")
    vendor_id = payload.get("sub")
    
    session = await db.get(VendorPortalLog, session_id)
    now = datetime.now(timezone.utc)
    sess_exp = session.expires_at if (session and session.expires_at and session.expires_at.tzinfo) else (session.expires_at.replace(tzinfo=timezone.utc) if session and session.expires_at else None)
    if not session or session.invalidated_at is not None or (sess_exp and sess_exp < now):
        raise HTTPException(status_code=401, detail="Session expired or invalid")
        
    return {"vendor_id": uuid.UUID(vendor_id), "session_id": uuid.UUID(session_id)}

@router.post("/login", response_model=VendorLoginResponse)
async def login(req: VendorLoginRequest, request: Request, db: AsyncSession = Depends(get_db)):
    return await authenticate_vendor(db, req, request.client.host, request.headers.get("user-agent", ""))

@router.post("/logout")
async def logout(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    await logout_vendor(db, vendor["session_id"])
    return {"message": "Logged out successfully"}

@router.get("/purchase-orders", response_model=list[POListResponse])
async def list_purchase_orders(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    await log_vendor_action(db, vendor["vendor_id"], "PO_LIST_VIEW")
    
    from app.models.rm_models import RmPoHistory
    has_updates_subq = (
        select(RmPoHistory.po_id)
        .where(RmPoHistory.action == 'UPDATED')
        .group_by(RmPoHistory.po_id)
        .subquery()
    )
    
    result = await db.execute(
        select(
            RmPurchaseOrder, 
            PoStatusMaster.name.label('status_name'),
            has_updates_subq.c.po_id.label('has_updates')
        )
        .join(PoStatusMaster, RmPurchaseOrder.status_id == PoStatusMaster.id)
        .outerjoin(has_updates_subq, RmPurchaseOrder.po_id == has_updates_subq.c.po_id)
        .where(RmPurchaseOrder.vendor_id == vendor["vendor_id"])
        .where(RmPurchaseOrder.status_id != None)
        .where(PoStatusMaster.code != 'DRAFT')
        .order_by(RmPurchaseOrder.created_at.desc())
    )
    rows = result.all()
    
    return [
        {
            "po_id": row.RmPurchaseOrder.po_id,
            "po_number": row.RmPurchaseOrder.po_number,
            "order_date": row.RmPurchaseOrder.order_date,
            "expected_delivery_date": row.RmPurchaseOrder.expected_delivery_date,
            "total_amount": row.RmPurchaseOrder.total_amount,
            "status": row.status_name,
            "has_updates": True if row.has_updates else False
        } for row in rows
    ]

import calendar
from datetime import date

@router.get("/scorecard", response_model=list[ScorecardResponse])
async def view_scorecard(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    from app.modules.vendor_portal.service import get_live_scorecard
    await log_vendor_action(db, vendor["vendor_id"], "SCORECARD_VIEW")
    
    today = date.today()
    start_date = date(today.year, today.month, 1)
    last_day = calendar.monthrange(today.year, today.month)[1]
    end_date = date(today.year, today.month, last_day)
    
    scorecard = await get_live_scorecard(db, vendor["vendor_id"], start_date, end_date)
    return [scorecard]

@router.get("/purchase-orders/{po_id}", response_model=PODetailResponse)
async def get_po_details(po_id: uuid.UUID, vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    po_result = await db.execute(
        select(RmPurchaseOrder, PoStatusMaster.code.label('status_code'))
        .outerjoin(PoStatusMaster, RmPurchaseOrder.status_id == PoStatusMaster.id)
        .where(RmPurchaseOrder.po_id == po_id)
        .where(RmPurchaseOrder.vendor_id == vendor["vendor_id"])
    )
    po_row = po_result.first()
    if not po_row:
        raise HTTPException(status_code=404, detail="PO not found")
        
    po = po_row.RmPurchaseOrder
    status_code = po_row.status_code or "OPEN"

    details_result = await db.execute(
        select(RmPoDetail, RmMaster)
        .join(RmMaster, RmPoDetail.rm_id == RmMaster.rm_id)
        .where(RmPoDetail.po_id == po_id)
    )
    
    lines = []
    for detail, rm in details_result.all():
        lines.append({
            "po_detail_id": detail.po_detail_id,
            "part_no": rm.part_no,
            "name": rm.name,
            "unit_of_measurement": rm.unit_of_measurement,
            "order_qty": detail.order_qty,
            "received_qty": detail.received_qty,
            "unit_price": detail.unit_price,
            "gst_percent": detail.gst_percent,
            "line_amount": detail.line_amount,
            "line_status": detail.line_status
        })
        
    return {
        "po_id": po.po_id,
        "po_number": po.po_number,
        "order_date": po.order_date,
        "expected_delivery_date": po.expected_delivery_date,
        "total_amount": po.total_amount,
        "notes": po.notes,
        "status": status_code,
        "details": lines
    }

@router.get("/asns", response_model=list[ASNListResponse])
async def list_asns(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    result = await db.execute(
        select(ASNLog, RmPurchaseOrder.po_number)
        .join(RmPurchaseOrder, ASNLog.po_id == RmPurchaseOrder.po_id)
        .where(ASNLog.vendor_id == vendor["vendor_id"])
        .order_by(ASNLog.created_at.desc())
    )
    rows = result.all()
    return [
        {
            "asn_id": row.ASNLog.asn_id,
            "asn_number": row.ASNLog.asn_number,
            "po_id": row.ASNLog.po_id,
            "po_number": row.po_number,
            "expected_date": row.ASNLog.expected_date,
            "delivery_mode": row.ASNLog.delivery_mode,
            "status": row.ASNLog.status
        } for row in rows
    ]

@router.get("/asns/{asn_id}", response_model=ASNResponse)
async def get_asn(asn_id: uuid.UUID, vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    from sqlalchemy.orm import selectinload
    
    result = await db.execute(
        select(ASNLog, RmPurchaseOrder.po_number)
        .options(selectinload(ASNLog.details))
        .join(RmPurchaseOrder, ASNLog.po_id == RmPurchaseOrder.po_id)
        .where(ASNLog.asn_id == asn_id, ASNLog.vendor_id == vendor["vendor_id"])
    )
    row = result.first()
    if not row:
        raise HTTPException(status_code=404, detail="ASN not found")
        
    asn_log = row.ASNLog
    
    # We need to get part_no and name for each detail
    details_list = []
    for d in asn_log.details:
        # fetch po_detail to get rm_id
        po_detail_res = await db.execute(
            select(RmPoDetail, RmMaster)
            .join(RmMaster, RmPoDetail.rm_id == RmMaster.rm_id)
            .where(RmPoDetail.po_detail_id == d.po_detail_id)
        )
        po_d_row = po_detail_res.first()
        if po_d_row:
            rm = po_d_row.RmMaster
            details_list.append({
                "asn_detail_id": d.asn_detail_id,
                "po_detail_id": d.po_detail_id,
                "part_no": rm.part_no,
                "name": rm.name,
                "qty_shipped": d.qty_shipped,
                "vendor_batch_ref": d.vendor_batch_ref
            })
            
    return {
        "asn_id": asn_log.asn_id,
        "asn_number": asn_log.asn_number,
        "po_id": asn_log.po_id,
        "po_number": row.po_number,
        "expected_date": asn_log.expected_date,
        "delivery_mode": asn_log.delivery_mode,
        "courier_name": asn_log.courier_name,
        "tracking_number": asn_log.tracking_number,
        "vehicle_number": asn_log.vehicle_number,
        "driver_name": asn_log.driver_name,
        "driver_phone": asn_log.driver_phone,
        "arrival_window": asn_log.arrival_window,
        "status": asn_log.status,
        "details": details_list
    }

@router.post("/asns", response_model=dict)
async def submit_asn(req: ASNSubmitRequest, vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    from app.modules.vendor_portal.service import generate_asn_number
    
    po_res = await db.execute(select(RmPurchaseOrder).where(RmPurchaseOrder.po_id == req.po_id, RmPurchaseOrder.vendor_id == vendor["vendor_id"]))
    po = po_res.scalar_one_or_none()
    if not po:
        raise HTTPException(status_code=404, detail="PO not found or unauthorized")
        
    existing_asns_res = await db.execute(select(ASNLog).where(ASNLog.po_id == req.po_id, ASNLog.status == 'SUBMITTED'))
    existing_asns = existing_asns_res.scalars().all()
    
    is_recreation = False
    for old_asn in existing_asns:
        old_asn.status = 'REPLACED_DUE_TO_DELAY'
        is_recreation = True

    status_code_to_find = 'UPDATED_ASN_SHIPPED' if is_recreation else 'ASN_SHIPPED'
    status_res = await db.execute(select(PoStatusMaster).where(PoStatusMaster.code == status_code_to_find))
    new_status = status_res.scalar_one_or_none()
    
    if new_status:
        await db.execute(update(RmPurchaseOrder).where(RmPurchaseOrder.po_id == po.po_id).values(status_id=new_status.id))
        
    asn_number = await generate_asn_number(db)
    
    asn_log = ASNLog(
        asn_number=asn_number,
        vendor_id=vendor["vendor_id"],
        po_id=req.po_id,
        expected_date=req.expected_date,
        delivery_mode=req.delivery_mode,
        courier_name=req.courier_name,
        tracking_number=req.tracking_number,
        vehicle_number=req.vehicle_number,
        driver_name=req.driver_name,
        arrival_window=req.arrival_window,
        status="SUBMITTED"
    )
    db.add(asn_log)
    await db.flush()
    
    for line in req.lines:
        detail = ASNDetail(
            asn_id=asn_log.asn_id,
            po_detail_id=line.po_detail_id,
            qty_shipped=line.qty_shipped,
            vendor_batch_ref=line.vendor_batch_ref
        )
        db.add(detail)
        
    await db.commit()
    await log_vendor_action(db, vendor["vendor_id"], "ASN_CREATED", "ASNLog", asn_log.asn_id)
    
    return {"message": "ASN successfully created!", "asn_id": asn_log.asn_id}

from pydantic import BaseModel
from typing import List, Optional

class RejectionResponse(BaseModel):
    rejection_id: uuid.UUID
    rln: str
    grn_id: uuid.UUID
    vendor_id: uuid.UUID
    total_qty: float
    reason_code: Optional[str] = None
    created_at: datetime
    status: str
    
class NCRResponse(BaseModel):
    ncr_id: uuid.UUID
    ncr_number: str
    vendor_id: uuid.UUID
    rejection_id: Optional[uuid.UUID]
    defect_description: Optional[str] = None
    car_due: Optional[datetime] = None
    status: str
    created_at: datetime

@router.get("/rejections", response_model=List[RejectionResponse])
async def get_rejections(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    result = await db.execute(
        select(RejectionLog)
        .where(RejectionLog.vendor_id == vendor["vendor_id"])
        .order_by(RejectionLog.created_at.desc())
    )
    return list(result.scalars().all())


@router.get("/ncrs", response_model=List[NCRResponse])
async def get_ncrs(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    result = await db.execute(
        select(NCRLog)
        .where(NCRLog.vendor_id == vendor["vendor_id"])
        .order_by(NCRLog.created_at.desc())
    )
    return list(result.scalars().all())

from app.models.vendor_portal import DebitNote
from app.modules.vendor_portal.schemas import DebitNoteResponse

@router.get("/debit-notes", response_model=List[DebitNoteResponse])
async def get_debit_notes(vendor: dict = Depends(get_current_vendor), db: AsyncSession = Depends(get_db)):
    result = await db.execute(
        select(DebitNote)
        .where(DebitNote.vendor_id == vendor["vendor_id"])
        .order_by(DebitNote.created_at.desc())
    )
    return list(result.scalars().all())

class AcknowledgeRequest(BaseModel):
    notes: Optional[str] = None

@router.post("/purchase-orders/{po_id}/acknowledge")
async def acknowledge_po(
    po_id: uuid.UUID,
    payload: AcknowledgeRequest,
    vendor: dict = Depends(get_current_vendor),
    db: AsyncSession = Depends(get_db)
):
    po = await db.get(RmPurchaseOrder, po_id)
    if not po or po.vendor_id != vendor["vendor_id"]:
        raise HTTPException(status_code=404, detail="PO not found")
        
    status_res = await db.execute(text("SELECT id FROM po_status_master WHERE code = 'ACKNOWLEDGED' LIMIT 1"))
    ack_status_id = status_res.scalar_one_or_none()
    
    if ack_status_id:
        po.status_id = ack_status_id
        
    tracking = POTrackingLog(
        po_id=po_id,
        event_type='ACKNOWLEDGED',
        event_date=datetime.utcnow(),
        notes=payload.notes,
        created_by=str(vendor["vendor_id"])
    )
    db.add(tracking)
    await db.commit()
    return {"message": "PO Acknowledged successfully"}


# ── MONTHLY PLAN (vendor read-only) ────────────────

@router.get("/monthly-plans", response_model=list[VendorMonthlyPlanResponse])
async def vendor_list_monthly_plans(
    vendor: dict = Depends(get_current_vendor),
    db: AsyncSession = Depends(get_db)
):
    """
    Returns all historical Monthly Procurement Plans visible to the authenticated vendor.
    Each line is enriched with RM name/UOM and live-computed ordered_qty from POs.
    Vendors see ALL months (historical and current), ordered newest first.
    """
    from datetime import date
    from sqlalchemy import func, and_
    from sqlalchemy.orm import selectinload

    vendor_id = vendor["vendor_id"]

    # Fetch all plans for this vendor
    plan_result = await db.execute(
        select(MonthlyProcurementPlan)
        .options(selectinload(MonthlyProcurementPlan.lines))
        .where(MonthlyProcurementPlan.vendor_id == vendor_id)
        .order_by(MonthlyProcurementPlan.plan_year.desc(), MonthlyProcurementPlan.plan_month.desc())
    )
    plans = plan_result.scalars().all()

    if not plans:
        return []

    # Collect all unique rm_ids across all plans
    all_rm_ids = {line.rm_id for plan in plans for line in plan.lines}

    # Fetch RM master info in one query
    rm_result = await db.execute(
        select(RmMaster).where(RmMaster.rm_id.in_(all_rm_ids))
    )
    rm_map = {str(rm.rm_id): rm for rm in rm_result.scalars().all()}

    output = []
    for plan in plans:
        first_day = date(plan.plan_year, plan.plan_month, 1)
        if plan.plan_month == 12:
            last_day = date(plan.plan_year + 1, 1, 1)
        else:
            last_day = date(plan.plan_year, plan.plan_month + 1, 1)

        # Live-compute ordered_qty per RM for this plan's vendor+month
        ordered_result = await db.execute(
            select(
                RmPoDetail.rm_id,
                func.coalesce(func.sum(RmPoDetail.order_qty), 0).label('total_ordered')
            )
            .join(RmPurchaseOrder, RmPoDetail.po_id == RmPurchaseOrder.po_id)
            .join(PoStatusMaster, RmPurchaseOrder.status_id == PoStatusMaster.id)
            .where(
                and_(
                    RmPurchaseOrder.vendor_id == vendor_id,
                    RmPurchaseOrder.order_date >= first_day,
                    RmPurchaseOrder.order_date < last_day,
                    PoStatusMaster.code.notin_(['DRAFT', 'CANCELLED'])
                )
            )
            .group_by(RmPoDetail.rm_id)
        )
        ordered_map = {str(r.rm_id): float(r.total_ordered) for r in ordered_result.all()}

        received_result = await db.execute(
            select(
                GrnDetail.rm_id,
                func.coalesce(func.sum(GrnDetail.received_qty), 0).label('total_received')
            )
            .join(RmReceivingLog, GrnDetail.grn_id == RmReceivingLog.grn_id)
            .join(RmPurchaseOrder, RmReceivingLog.po_id == RmPurchaseOrder.po_id)
            .where(
                and_(
                    RmPurchaseOrder.vendor_id == vendor_id,
                    RmReceivingLog.received_date >= first_day,
                    RmReceivingLog.received_date < last_day,
                )
            )
            .group_by(GrnDetail.rm_id)
        )
        received_map = {str(r.rm_id): float(r.total_received) for r in received_result.all()}

        enriched_lines = []
        for line in plan.lines:
            rm = rm_map.get(str(line.rm_id))
            enriched_lines.append(VendorMonthlyPlanLineResponse(
                line_id=line.line_id,
                rm_id=line.rm_id,
                rm_name=rm.name if rm else 'Unknown RM',
                rm_part_no=rm.part_no if rm else None,
                rm_uom=rm.unit_of_measurement if rm else '',
                planned_qty=float(line.planned_qty),
                ordered_qty=ordered_map.get(str(line.rm_id), 0.0),
                received_qty=received_map.get(str(line.rm_id), 0.0),
            ))

        output.append(VendorMonthlyPlanResponse(
            plan_id=plan.plan_id,
            plan_year=plan.plan_year,
            plan_month=plan.plan_month,
            notes=plan.notes,
            lines=enriched_lines,
        ))

    return output
