from fastapi import APIRouter, HTTPException, status, Depends, Response, Query
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import date as Date, datetime
from uuid import uuid4
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError

from app.core.database import get_db
from app.core.security import get_current_user
from app.core.dependencies import get_current_company_id
from app.models.branch import Branch
from app.models.sales import Customer, Quotation, QuotationItem as DBQuotationItem
from app.models.inventory import Product
from app.services.document_printing_service import DocumentPrintingService
from app.utils.logger import get_logger, log_exception, log_error_with_context

logger = get_logger(__name__)

router = APIRouter()


class QuotationItemSchema(BaseModel):
    product_id: Optional[str] = None  # Made optional for custom items
    description: Optional[str] = None  # For custom text (labor, services, etc.)
    quantity: float
    price: float
    discount: float = 0.0


class QuotationCreate(BaseModel):
    customer_id: Optional[str] = None  # Optional for walk-in customers
    date: Date
    valid_until: Optional[Date] = None
    reference: Optional[str] = None
    notes: Optional[str] = None
    items: List[QuotationItemSchema]
    subtotal: float
    vat: float
    total: float
    status: str = Field(..., pattern=r"^(created|draft)$")
    branch_id: Optional[str] = None  # Optional override to create quote for another branch
    save_pdf: bool = Field(True, description="Save named PDF under /static/documents/quotations/")
    send_whatsapp: bool = Field(False, description="Send quotation via WhatsApp after creation")
    whatsapp_phone: Optional[str] = None


class QuotationUpdate(BaseModel):
    customer_id: Optional[str] = None  # Optional for walk-in customers
    date: Optional[Date] = None
    valid_until: Optional[Date] = None
    reference: Optional[str] = None
    notes: Optional[str] = None
    items: Optional[List[QuotationItemSchema]] = None
    subtotal: Optional[float] = None
    vat: Optional[float] = None
    total: Optional[float] = None
    status: Optional[str] = None


class ConvertToInvoiceRequest(BaseModel):
    """Request schema for converting quotation to invoice"""
    branch_id: Optional[str] = None  # Override user's default branch if needed
    payment_terms: Optional[int] = 30  # Days until due


def _quotation_to_dict(quotation: Quotation, db: Session) -> dict:
    """Convert a Quotation ORM object to a dictionary with enriched data"""
    items_list = []
    for item in quotation.items:
        # Use description if available, otherwise use product name
        description = item.description if item.description else (item.product.name if item.product else None)
        items_list.append({
            "product_id": item.product_id,
            "product_name": item.product.name if item.product else None,
            "description": description,
            "quantity": float(item.quantity),
            "price": float(item.price),
            "discount": float(item.discount),
            "line_total": float(item.line_total) if item.line_total else 0.0,
        })

    return {
        "id": quotation.id,
        "quote_number": quotation.quote_number,
        "customer_id": quotation.customer_id,
        "customer_name": quotation.customer.name if quotation.customer else None,
        "date": quotation.date.isoformat() if quotation.date else None,
        "valid_until": quotation.valid_until.isoformat() if quotation.valid_until else None,
        "reference": quotation.reference,
        "notes": quotation.notes,
        "status": quotation.status,
        "subtotal": float(quotation.subtotal),
        "vat": float(quotation.vat),
        "total": float(quotation.total),
        "branch_id": quotation.branch_id,
        "created_by": quotation.created_by,
        "created_at": quotation.created_at.isoformat() if quotation.created_at else None,
        "updated_at": quotation.updated_at.isoformat() if quotation.updated_at else None,
        "items": items_list,
    }


@router.post("", status_code=status.HTTP_201_CREATED)
def create_quotation(
    quotation: QuotationCreate,
    current_user = Depends(get_current_user),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    try:
        # Verify user has a branch assigned
        if not current_user.branch_id:
            raise HTTPException(
                status_code=400,
                detail="User must be assigned to a branch to create quotations"
            )

        # Determine branch: explicit payload override > user branch > session branch > first active branch
        branch_id = quotation.branch_id or current_user.branch_id
        branch = db.query(Branch).filter(Branch.id == branch_id).first() if branch_id else None

        if not branch:
            session_branch_id = getattr(current_user, "session_branch_id", None)
            if session_branch_id:
                branch = db.query(Branch).filter(Branch.id == session_branch_id).first()
                if branch:
                    branch_id = branch.id

            if not branch:
                _bq = db.query(Branch).filter(Branch.active == True)
                if company_id:
                    _bq = _bq.filter(Branch.company_id == company_id)
                branch = _bq.order_by(Branch.is_head_office.desc()).first()
                if branch:
                    branch_id = branch.id

            if not branch:
                raise HTTPException(
                    status_code=400,
                    detail="User branch is invalid and no active branches are available. Please select or create a branch."
                )

            logger.warning(
                "Branch override %s or user branch %s not found; using fallback branch_id %s",
                quotation.branch_id,
                current_user.branch_id,
                branch_id,
            )

        # Generate unique quote number
        # Find the highest sequence number for this date
        date_prefix = f"Q-{quotation.date.strftime('%Y%m%d')}-"
        existing_quotes = db.query(Quotation).filter(
            Quotation.quote_number.like(f"{date_prefix}%")
        ).all()

        # Extract sequence numbers and find the max
        max_sequence = 0
        for q in existing_quotes:
            try:
                seq_str = q.quote_number.split('-')[-1]
                seq_num = int(seq_str)
                if seq_num > max_sequence:
                    max_sequence = seq_num
            except (ValueError, IndexError):
                continue

        sequence = max_sequence + 1
        quote_number = f"{date_prefix}{sequence:04d}"
        save_pdf_flag = quotation.save_pdf
        send_whatsapp_flag = quotation.send_whatsapp
        whatsapp_phone_override = quotation.whatsapp_phone

        # Create quotation record with user and branch attribution
        db_quotation = Quotation(
            id=str(uuid4()),
            quote_number=quote_number,
            customer_id=quotation.customer_id,
            date=quotation.date,
            valid_until=quotation.valid_until,
            reference=quotation.reference,
            notes=quotation.notes,
            status=quotation.status,
            subtotal=quotation.subtotal,
            vat=quotation.vat,
            total=quotation.total,
            created_by=current_user.id,
            branch_id=branch_id,
        )
        db.add(db_quotation)
        db.flush()

        # Create quotation items
        for item in quotation.items:
            line_total = item.quantity * item.price * (1 - item.discount / 100)
            db_item = DBQuotationItem(
                id=str(uuid4()),
                quotation_id=db_quotation.id,
                product_id=item.product_id,
                description=item.description,
                quantity=item.quantity,
                price=item.price,
                discount=item.discount,
                line_total=line_total,
            )
            db.add(db_item)

        db.commit()
        db.refresh(db_quotation)

        result = {"success": True, "quotation": _quotation_to_dict(db_quotation, db)}

        if save_pdf_flag or send_whatsapp_flag:
            try:
                from sqlalchemy.orm import joinedload
                from app.models.sales import Quotation as QModel
                from app.services.document_delivery_service import (
                    DocumentDeliveryService,
                    DocumentDeliveryOptions,
                )
                q_full = db.query(QModel).options(joinedload(QModel.customer)).filter(
                    QModel.id == db_quotation.id
                ).first()
                delivery = DocumentDeliveryService(db).deliver_quotation(
                    q_full,
                    DocumentDeliveryOptions(
                        save_pdf=save_pdf_flag,
                        send_whatsapp=send_whatsapp_flag,
                        whatsapp_phone=whatsapp_phone_override,
                    ),
                )
                result["pdf_delivery"] = delivery.to_dict()
            except Exception as del_err:
                result["pdf_delivery"] = {"error": str(del_err)}

        return result
    except HTTPException:
        raise
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'quote_number' in error_msg:
                raise HTTPException(status_code=400, detail="A quotation with this quote number already exists")
            else:
                raise HTTPException(status_code=400, detail="A record with this value already exists. Please ensure unique fields are different.")
        raise HTTPException(status_code=400, detail=f"Database constraint violation: {error_msg}")
    except Exception as e:
        db.rollback()
        import traceback
        traceback.print_exc()
        raise HTTPException(
            status_code=500,
            detail=f"Failed to create quotation: {str(e)}"
        )


@router.get("", description="List all quotations or filter by customer/status")
def list_quotations(
    customer_id: Optional[str] = Query(None, description="Filter by customer ID"),
    status: Optional[str] = Query(None, description="Filter by status (created, draft, accepted, sent, expired)"),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    query = db.query(Quotation).order_by(Quotation.date.desc())

    if company_id:
        branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
        query = query.filter(Quotation.branch_id.in_(branch_ids_sq))
    if customer_id:
        query = query.filter(Quotation.customer_id == customer_id)
    if status:
        query = query.filter(Quotation.status == status)

    quotations = query.all()
    quotations_list = [_quotation_to_dict(q, db) for q in quotations]
    return {"success": True, "quotations": quotations_list}


@router.get("/{quotation_id}")
def get_quotation(quotation_id: str, db: Session = Depends(get_db)):
    quotation = db.query(Quotation).filter(
        (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
    ).first()

    if not quotation:
        raise HTTPException(status_code=404, detail="Quotation not found")

    return {"success": True, "quotation": _quotation_to_dict(quotation, db)}


@router.post("/{quotation_id}/apply-to-sale")
def apply_quotation_to_sale(
    quotation_id: str,
    sale_data: dict,
    db: Session = Depends(get_db)
):
    """
    Apply a quotation to a new sale/POS transaction.
    Returns quotation items formatted for immediate sale.
    """
    from app.models.sales import Sale

    quotation = db.query(Quotation).filter(
        (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
    ).first()

    if not quotation:
        raise HTTPException(status_code=404, detail="Quotation not found")

    # Check quotation is in valid state for conversion
    if quotation.status not in ['created', 'sent', 'accepted']:
        raise HTTPException(
            status_code=400,
            detail=f"Cannot apply quotation with status '{quotation.status}'. Only 'created', 'sent', or 'accepted' quotations can be used."
        )

    # Format quotation items for sale/POS
    formatted_items = []
    for item in quotation.items:
        formatted_items.append({
            "product_id": item.product_id,
            "product_name": item.product.name if item.product else item.description,
            "description": item.description if item.description else (item.product.name if item.product else None),
            "quantity": float(item.quantity),
            "unit_price": float(item.price),
            "price": float(item.price),
            "discount_percentage": float(item.discount),
            "line_total": float(item.line_total) if item.line_total else 0.0,
        })

    return {
        "success": True,
        "quotation": _quotation_to_dict(quotation, db),
        "formatted_items": formatted_items,
        "message": f"Quotation {quotation.quote_number} loaded. Proceed to complete the sale."
    }


@router.post("/{quotation_id}/apply-to-invoice")
def apply_quotation_to_invoice(
    quotation_id: str,
    invoice_data: dict,
    db: Session = Depends(get_db)
):
    """
    Apply a quotation to a new invoice.
    Returns quotation items formatted for immediate invoice creation.
    """
    from app.models.sales import Invoice

    quotation = db.query(Quotation).filter(
        (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
    ).first()

    if not quotation:
        raise HTTPException(status_code=404, detail="Quotation not found")

    # Check quotation is in valid state for conversion
    if quotation.status not in ['created', 'sent', 'accepted']:
        raise HTTPException(
            status_code=400,
            detail=f"Cannot apply quotation with status '{quotation.status}'. Only 'created', 'sent', or 'accepted' quotations can be used."
        )

    # Format quotation items for invoice
    formatted_items = []
    for item in quotation.items:
        formatted_items.append({
            "product_id": item.product_id,
            "product_name": item.product.name if item.product else item.description,
            "description": item.description if item.description else (item.product.name if item.product else None),
            "quantity": float(item.quantity),
            "unit_price": float(item.price),
            "price": float(item.price),
            "discount_percentage": float(item.discount),
            "line_total": float(item.line_total) if item.line_total else 0.0,
        })

    return {
        "success": True,
        "quotation": _quotation_to_dict(quotation, db),
        "formatted_items": formatted_items,
        "message": f"Quotation {quotation.quote_number} loaded. Proceed to complete the invoice."
    }


@router.get("/{quotation_id}/print")
def print_quotation_pdf(quotation_id: str, db: Session = Depends(get_db)):
    quotation = db.query(Quotation).filter(
        (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
    ).first()

    if not quotation:
        raise HTTPException(status_code=404, detail="Quotation not found")

    enriched_rec = _quotation_to_dict(quotation, db)
    service = DocumentPrintingService(db)
    fmt, content, filename = service.generate_document_pdf(
        document_type="quote",
        document_id=enriched_rec.get("quote_number") or quotation_id,
        title=f"Quotation {enriched_rec.get('quote_number', '')}",
        content_data=enriched_rec,
    )
    return Response(content=content, media_type="application/pdf", headers={
        "Content-Disposition": f'attachment; filename="{filename}"'
    })


@router.put("/{quotation_id}")
def update_quotation(quotation_id: str, quotation: QuotationUpdate, db: Session = Depends(get_db)):
    try:
        db_quotation = db.query(Quotation).filter(
            (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
        ).first()

        if not db_quotation:
            raise HTTPException(status_code=404, detail="Quotation not found")

        # Update quotation fields
        update_data = quotation.dict(exclude_unset=True, exclude={"items"})
        for field, value in update_data.items():
            setattr(db_quotation, field, value)

        # Update items if provided
        if quotation.items is not None:
            # Delete existing items
            db.query(DBQuotationItem).filter(DBQuotationItem.quotation_id == db_quotation.id).delete()

            # Create new items
            for item in quotation.items:
                line_total = item.quantity * item.price * (1 - item.discount / 100)
                db_item = DBQuotationItem(
                    id=str(uuid4()),
                    quotation_id=db_quotation.id,
                    product_id=item.product_id,
                    description=item.description,
                    quantity=item.quantity,
                    price=item.price,
                    discount=item.discount,
                    line_total=line_total,
                )
                db.add(db_item)

        db.commit()
        db.refresh(db_quotation)

        return {"success": True, "quotation": _quotation_to_dict(db_quotation, db)}
    except HTTPException:
        raise
    except IntegrityError as e:
        db.rollback()
        error_msg = str(e.orig)
        if 'duplicate key' in error_msg.lower() or 'unique constraint' in error_msg.lower():
            if 'quote_number' in error_msg:
                raise HTTPException(status_code=400, detail="A quotation with this quote number already exists")
            else:
                raise HTTPException(status_code=400, detail="A record with this value already exists. Please ensure unique fields are different.")
        raise HTTPException(status_code=400, detail=f"Database constraint violation: {error_msg}")
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error updating quotation: {str(e)}")
@router.delete("/{quotation_id}")
def delete_quotation(quotation_id: str, db: Session = Depends(get_db)):
    db_quotation = db.query(Quotation).filter(
        (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
    ).first()

    if not db_quotation:
        raise HTTPException(status_code=404, detail="Quotation not found")

    deleted_data = _quotation_to_dict(db_quotation, db)
    db.delete(db_quotation)
    db.commit()

    return {"success": True, "message": "Quotation deleted", "quotation": deleted_data}


class CreateJobCardFromQuotationRequest(BaseModel):
    branch_id: Optional[str] = None
    job_type: str = "service"
    technician_id: Optional[str] = None
    description: Optional[str] = None
    notes: Optional[str] = None


@router.post("/{quotation_id}/create-job-card")
def create_job_card_from_quotation(
    quotation_id: str,
    request: CreateJobCardFromQuotationRequest = None,
    db: Session = Depends(get_db),
):
    """Create a job card from a quotation (non-stock lines, labour, and stocked items)."""
    from app.services.job_card_service import JobCardService

    body = request.model_dump(exclude_unset=True) if request else {}
    try:
        job = JobCardService(db).create_from_quotation(
            quotation_id,
            branch_id=body.get("branch_id"),
            job_type=body.get("job_type", "service"),
            technician_id=body.get("technician_id"),
            description=body.get("description"),
            notes=body.get("notes"),
        )
        return {
            "success": True,
            "message": f"Job card {job.get('job_number')} created from quotation",
            "job_card": job,
        }
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.post("/{quotation_id}/convert-to-invoice")
def convert_quotation_to_invoice(
    quotation_id: str,
    request: ConvertToInvoiceRequest = None,
    current_user = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """
    Convert an accepted quotation into an invoice with inventory tracking.

    - Creates invoice from quotation
    - Deducts inventory from branch allocation
    - Creates audit trail via InventoryTransaction
    - Tracks user who created the invoice
    - Uses authenticated user's context for attribution
    """
    from app.models.sales import Invoice, InvoiceItem as DBInvoiceItem
    from app.models.inventory import InventoryTransaction
    from app.models.inventory_allocation import BranchInventoryAllocation
    from datetime import timedelta

    # Get the quotation
    quotation = db.query(Quotation).filter(
        (Quotation.id == quotation_id) | (Quotation.quote_number == quotation_id)
    ).first()

    if not quotation:
        raise HTTPException(status_code=404, detail="Quotation not found")

    # Check if quotation is in a valid state
    if quotation.status not in ['created', 'sent', 'accepted']:
        raise HTTPException(
            status_code=400,
            detail=f"Cannot convert quotation with status '{quotation.status}'. Only 'created', 'sent', or 'accepted' quotations can be converted."
        )

    # Check if already converted
    existing_invoice = db.query(Invoice).filter(Invoice.quotation_id == quotation.id).first()
    if existing_invoice:
        raise HTTPException(
            status_code=400,
            detail=f"Quotation already converted to invoice {existing_invoice.invoice_number}"
        )

    # Use authenticated user's context for attribution
    if request is None:
        request = ConvertToInvoiceRequest()

    # Get user ID from authenticated user
    # Verify that the user actually exists in the database
    from app.models.user import User
    user_id = None
    if current_user.id:
        existing_user = db.query(User).filter(User.id == current_user.id).first()
        if existing_user:
            user_id = current_user.id

    # Get branch ID: quotation's branch > request override > user's branch
    # Priority: Use the branch from the quotation being converted
    branch_id = quotation.branch_id or request.branch_id or current_user.branch_id

    if not branch_id:
        raise HTTPException(
            status_code=400,
            detail="Cannot determine branch for invoice. Quotation must be assigned to a branch."
        )

    payment_days = request.payment_terms or 30

    try:
        # Generate invoice number
        today = datetime.now().date()
        existing_count = db.query(Invoice).filter(
            Invoice.date == today
        ).count()
        sequence = existing_count + 1
        invoice_number = f"INV-{today.strftime('%Y%m%d')}-{sequence:04d}"

        # Create invoice
        invoice = Invoice(
            id=str(uuid4()),
            invoice_number=invoice_number,
            quotation_id=quotation.id,
            customer_id=quotation.customer_id,
            date=today,
            due_date=today + timedelta(days=payment_days),
            status='pending',
            total=float(quotation.total),
            total_vat_amount=float(quotation.vat),
            total_amount=float(quotation.total),
            amount_paid=0.0,
            branch_id=branch_id,
            created_by=user_id,
            notes=f"Converted from quotation {quotation.quote_number}\n{quotation.notes or ''}".strip()
        )
        db.add(invoice)
        db.flush()

        # Create invoice items and handle inventory deduction
        inventory_errors = []

        for q_item in quotation.items:
            # Create invoice item
            inv_item = DBInvoiceItem(
                id=str(uuid4()),
                invoice_id=invoice.id,
                product_id=q_item.product_id,
                description=q_item.description if q_item.description else (q_item.product.name if q_item.product else None),
                quantity=float(q_item.quantity),
                price=float(q_item.price),
                total=float(q_item.line_total),
                vat_amount=float(q_item.line_total) * 0.14,  # Assuming 14% VAT
                discount_percentage=float(q_item.discount),
            )
            db.add(inv_item)

            # Handle inventory deduction for product items (not custom text items)
            if q_item.product_id:
                # Get or create branch allocation for this product
                allocation = db.query(BranchInventoryAllocation).filter(
                    BranchInventoryAllocation.product_id == q_item.product_id,
                    BranchInventoryAllocation.branch_id == branch_id
                ).first()

                if not allocation:
                    # Create allocation on-demand if it doesn't exist
                    # This allows selling products that weren't previously allocated to this branch
                    allocation = BranchInventoryAllocation(
                        id=str(uuid4()),
                        product_id=q_item.product_id,
                        branch_id=branch_id,
                        allocated_quantity=0,
                        received_quantity=0,
                        available_quantity=0,  # Will go negative when deducted
                        reserved_quantity=0,
                        allocated_cost_per_unit=0,
                        total_allocated_cost=0
                    )
                    db.add(allocation)
                    logger.info(
                        f"Created on-demand allocation for product {q_item.product_id} "
                        f"in branch {branch_id} during invoice conversion"
                    )

                # Record previous state for audit trail
                previous_quantity = allocation.available_quantity

                # Deduct from allocation (allows negative inventory to track stockouts)
                allocation.available_quantity -= float(q_item.quantity)

                # Create inventory transaction for complete audit trail
                inv_transaction = InventoryTransaction(
                    id=str(uuid4()),
                    product_id=q_item.product_id,
                    branch_id=branch_id,
                    transaction_type='sale',
                    quantity=-int(q_item.quantity),  # Negative for outgoing
                    reference=f"Invoice {invoice_number}",
                    note=f"Sale via invoice {invoice_number} (converted from quotation {quotation.quote_number})",
                    created_by=user_id,
                    date=today,
                    previous_quantity=int(previous_quantity),
                    new_quantity=int(allocation.available_quantity)
                )
                db.add(inv_transaction)

                # Log warning if negative inventory (stockout situation)
                if allocation.available_quantity < 0:
                    logger.warning(
                        f"Negative inventory for product {q_item.product_id} "
                        f"at branch {branch_id}: {allocation.available_quantity} units. "
                        f"This indicates a stockout situation that needs restocking."
                    )
        quotation.status = 'converted'

        db.commit()
        db.refresh(invoice)

        return {
            "success": True,
            "message": f"Quotation {quotation.quote_number} converted to invoice {invoice.invoice_number}",
            "invoice": {
                "id": invoice.id,
                "invoice_number": invoice.invoice_number,
                "total": float(invoice.total),
                "status": invoice.status,
                "branch_id": branch_id,
                "created_by": user_id
            }
        }

    except Exception as e:
        db.rollback()
        import traceback
        error_msg = str(e) or f"{type(e).__name__}: {repr(e)}"
        logger.error(f"Quotation conversion error: {error_msg}")
        traceback.print_exc()
        raise HTTPException(
            status_code=500,
            detail=f"Failed to convert quotation to invoice: {error_msg}"
        )

