"""
Billing API Endpoints

Comprehensive billing management for rentals, utilities, subscriptions, and usage-based billing.
"""

from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import PlainTextResponse
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
from datetime import date
from decimal import Decimal

from app.core.database import get_db
from app.core.dependencies import get_current_company_id
from app.models.branch import Branch
from app.models.billing import BillingCycle, BillableItem
from app.models.sales import Customer
from app.services.billing_service import BillingService
from app.services.billing_property_schedule_service import (
    get_property_rental_schedule,
    save_property_rental_schedule,
    mark_schedule_run,
)

from app.utils.logger import get_logger, log_exception, log_error_with_context

logger = get_logger(__name__)

router = APIRouter()


# ============================================================================
# SCHEMAS
# ============================================================================

class BillingCycleCreate(BaseModel):
    """Billing cycle creation schema"""
    name: str
    customer_id: str
    cycle_type: str = Field(..., description="monthly, quarterly, annual, custom")
    interval: str
    interval_count: int = 1
    start_date: Optional[date] = None
    end_date: Optional[date] = None
    description: Optional[str] = None


class BillingCycleResponse(BaseModel):
    """Billing cycle response schema"""
    id: str
    name: str
    customer_id: str
    cycle_type: str
    interval: str
    interval_count: int
    start_date: date
    end_date: Optional[date]
    status: str
    description: Optional[str]

    class Config:
        from_attributes = True


class BillableItemCreate(BaseModel):
    """Billable item creation schema"""
    billing_cycle_id: str
    billable_type: str = Field(
        ...,
        description="rental_property, rental_vehicle, utility_water, utility_electricity, subscription_service, usage_minutes, license, tax, etc."
    )
    billable_id: str = Field(..., description="Product ID")
    amount: Decimal
    start_date: date
    end_date: Optional[date] = None
    description: Optional[str] = None
    meta_data: Optional[Dict] = None


class BillableItemResponse(BaseModel):
    """Billable item response schema"""
    id: str
    billing_cycle_id: str
    billable_type: str
    billable_id: str
    amount: Decimal
    description: Optional[str]
    start_date: date
    end_date: Optional[date]
    status: str
    meta_data: Optional[Dict]
    license_number: Optional[str]
    license_type: Optional[str]
    license_expiry_date: Optional[date]

    class Config:
        from_attributes = True


class MeterReadingCreate(BaseModel):
    """Meter reading creation schema"""
    reading_date: date
    reading_value: Decimal
    notes: Optional[str] = None


class MeterReadingResponse(BaseModel):
    """Meter reading response schema"""
    last_reading: float
    current_reading: float
    usage: float
    rate: float
    amount: float
    reading_date: str


class InvoiceGenerationResponse(BaseModel):
    """Invoice generation response schema"""
    count: int
    invoices: List[Dict]


class DashboardStatsResponse(BaseModel):
    """Dashboard statistics response schema"""
    active_rentals: int
    rentals_revenue: float
    property_rentals: int = 0
    property_rentals_revenue: float = 0.0
    utility_meters: int
    pending_readings: int
    subscriptions: int
    subscriptions_revenue: float
    usage_items: int
    usage_revenue: float
    total_active_items: int


class PropertyRentalScheduleUpdate(BaseModel):
    enabled: Optional[bool] = None
    frequency: Optional[str] = Field(None, description="monthly or quarterly")
    day_of_month: Optional[int] = Field(None, ge=1, le=28)
    time: Optional[str] = None
    recipients: Optional[List[str]] = None


# ============================================================================
# BILLING CYCLES
# ============================================================================

@router.post("/cycles", response_model=BillingCycleResponse)
async def create_billing_cycle(
    cycle_data: BillingCycleCreate,
    db: Session = Depends(get_db)
):
    """Create a new billing cycle"""
    try:
        billing_service = BillingService(db)
        cycle = billing_service.create_billing_cycle(
            name=cycle_data.name,
            customer_id=cycle_data.customer_id,
            cycle_type=cycle_data.cycle_type,
            interval=cycle_data.interval,
            interval_count=cycle_data.interval_count,
            start_date=cycle_data.start_date,
            end_date=cycle_data.end_date,
            description=cycle_data.description
        )
        return cycle
    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 'name' in error_msg:
                raise HTTPException(status_code=400, detail="A billing cycle with this name already exists")
            else:
                raise HTTPException(status_code=400, detail="A billing cycle 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:
        raise HTTPException(status_code=500, detail=f"Error creating billing cycle: {str(e)}")


@router.get("/cycles", response_model=List[BillingCycleResponse])
async def get_billing_cycles(
    customer_id: Optional[str] = Query(None),
    status: Optional[str] = Query(None),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get billing cycles with optional filters"""
    try:
        logger.info(f"Getting billing cycles - customer_id: {customer_id}, status: {status}")
        if company_id and not customer_id:
            from app.models.sales import Customer
            branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            customer_ids_sq = db.query(Customer.id).filter(Customer.branch_id.in_(branch_ids_sq))
            q = db.query(BillingCycle).filter(BillingCycle.customer_id.in_(customer_ids_sq))
            if status:
                q = q.filter(BillingCycle.status == status)
            cycles = q.all()
        else:
            billing_service = BillingService(db)
            cycles = billing_service.get_billing_cycles(
                customer_id=customer_id,
                status=status
            )
        logger.info(f"Found {len(cycles)} billing cycles")
        return cycles
    except Exception as e:
        logger.error(f"Error retrieving billing cycles: {str(e)}", exc_info=True)
        log_exception(logger, e, str({"endpoint": "/cycles", "customer_id": customer_id, "status": status}))
        raise HTTPException(status_code=500, detail=f"Error retrieving billing cycles: {str(e)}")


@router.get("/cycles/{cycle_id}", response_model=BillingCycleResponse)
async def get_billing_cycle(
    cycle_id: str,
    db: Session = Depends(get_db)
):
    """Get a specific billing cycle"""
    try:
        billing_service = BillingService(db)
        cycle = billing_service.get_billing_cycle(cycle_id)
        if not cycle:
            raise HTTPException(status_code=404, detail=f"Billing cycle {cycle_id} not found")
        return cycle
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error retrieving billing cycle: {str(e)}")


# ============================================================================
# BILLABLE ITEMS
# ============================================================================

@router.post("/items", response_model=BillableItemResponse)
async def create_billable_item(
    item_data: BillableItemCreate,
    db: Session = Depends(get_db)
):
    """Create a new billable item (rental, utility, subscription, etc.)"""
    try:
        billing_service = BillingService(db)
        item = billing_service.create_billable_item(
            billing_cycle_id=item_data.billing_cycle_id,
            billable_type=item_data.billable_type,
            billable_id=item_data.billable_id,
            amount=item_data.amount,
            start_date=item_data.start_date,
            end_date=item_data.end_date,
            description=item_data.description,
            meta_data=item_data.meta_data
        )
        return item
    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():
            raise HTTPException(status_code=400, detail="A billable item 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:
        raise HTTPException(status_code=500, detail=f"Error creating billable item: {str(e)}")


@router.get("/items", response_model=List[BillableItemResponse])
async def get_billable_items(
    billing_cycle_id: Optional[str] = Query(None),
    billable_type: Optional[str] = Query(None),
    status: Optional[str] = Query(None),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get billable items with optional filters"""
    try:
        logger.info(f"Getting billable items - cycle_id: {billing_cycle_id}, type: {billable_type}, status: {status}")
        if company_id and not billing_cycle_id:
            branch_ids_sq = db.query(Branch.id).filter(Branch.company_id == company_id)
            customer_ids_sq = db.query(Customer.id).filter(Customer.branch_id.in_(branch_ids_sq))
            cycle_ids_sq = db.query(BillingCycle.id).filter(BillingCycle.customer_id.in_(customer_ids_sq))
            q = db.query(BillableItem).filter(BillableItem.billing_cycle_id.in_(cycle_ids_sq))
            if billable_type:
                q = q.filter(BillableItem.billable_type == billable_type)
            if status:
                q = q.filter(BillableItem.status == status)
            items = q.all()
        else:
            billing_service = BillingService(db)
            items = billing_service.get_billable_items(
                billing_cycle_id=billing_cycle_id,
                billable_type=billable_type,
                status=status
            )
        logger.info(f"Found {len(items)} billable items")
        return items
    except Exception as e:
        logger.error(f"Error retrieving billable items: {str(e)}", exc_info=True)
        log_exception(logger, e, str({"endpoint": "/items", "cycle_id": billing_cycle_id}))
        raise HTTPException(status_code=500, detail=f"Error retrieving billable items: {str(e)}")


@router.get("/items/{item_id}", response_model=BillableItemResponse)
async def get_billable_item(
    item_id: str,
    db: Session = Depends(get_db)
):
    """Get a specific billable item"""
    try:
        billing_service = BillingService(db)
        item = billing_service.get_billable_item(item_id)
        if not item:
            raise HTTPException(status_code=404, detail=f"Billable item {item_id} not found")
        return item
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error retrieving billable item: {str(e)}")


@router.put("/items/{item_id}", response_model=BillableItemResponse)
async def update_billable_item(
    item_id: str,
    update_data: Dict,
    db: Session = Depends(get_db)
):
    """Update a billable item"""
    try:
        billing_service = BillingService(db)
        item = billing_service.update_billable_item(item_id, **update_data)
        return item
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error updating billable item: {str(e)}")


# ============================================================================
# METER READINGS
# ============================================================================

@router.post("/items/{item_id}/readings", response_model=MeterReadingResponse)
async def record_meter_reading(
    item_id: str,
    reading_data: MeterReadingCreate,
    db: Session = Depends(get_db)
):
    """Record a meter reading for a utility item"""
    try:
        billing_service = BillingService(db)
        result = billing_service.record_meter_reading(
            item_id=item_id,
            reading_date=reading_data.reading_date,
            reading_value=reading_data.reading_value,
            notes=reading_data.notes
        )
        return result
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error recording meter reading: {str(e)}")


# ============================================================================
# INVOICE GENERATION
# ============================================================================

@router.post("/cycles/{cycle_id}/generate-invoices")
async def generate_invoices_for_cycle(
    cycle_id: str,
    invoice_date: Optional[date] = None,
    created_by: Optional[str] = Query(None),
    db: Session = Depends(get_db)
):
    """Generate invoices for a specific billing cycle"""
    try:
        billing_service = BillingService(db)
        invoices = billing_service.generate_invoices_for_cycle(
            billing_cycle_id=cycle_id,
            invoice_date=invoice_date,
            created_by=created_by
        )
        return {
            'count': len(invoices),
            'invoices': [
                {
                    'id': inv.id,
                    'invoice_number': inv.invoice_number,
                    'customer_id': inv.customer_id,
                    'total_amount': float(inv.total_amount)
                }
                for inv in invoices
            ]
        }
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error generating invoices: {str(e)}")


@router.post("/generate-invoices", response_model=InvoiceGenerationResponse)
async def generate_all_due_invoices(
    reference_date: Optional[date] = Query(None),
    created_by: Optional[str] = Query(None),
    db: Session = Depends(get_db)
):
    """Generate invoices for all billing cycles that are due"""
    try:
        billing_service = BillingService(db)
        result = billing_service.generate_all_due_invoices(
            reference_date=reference_date,
            created_by=created_by
        )
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error generating invoices: {str(e)}")


# ============================================================================
# DASHBOARD & REPORTING
# ============================================================================

@router.get("/dashboard", response_model=DashboardStatsResponse)
async def get_dashboard_stats(
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Get billing dashboard statistics"""
    try:
        logger.info("Getting billing dashboard stats")
        billing_service = BillingService(db)
        stats = billing_service.get_dashboard_stats()
        property_rows = billing_service.get_property_rental_report(company_id=company_id)
        stats['property_rentals'] = len(property_rows)
        stats['property_rentals_revenue'] = sum(r['monthly_rent'] for r in property_rows)
        logger.info(f"Dashboard stats retrieved successfully: {stats}")
        return stats
    except Exception as e:
        logger.error(f"Error retrieving dashboard stats: {str(e)}", exc_info=True)
        log_exception(logger, e, str({"endpoint": "/dashboard", "method": "GET"}))
        raise HTTPException(status_code=500, detail=f"Error retrieving dashboard stats: {str(e)}")


@router.get("/customers/{customer_id}/summary")
async def get_customer_billing_summary(
    customer_id: str,
    db: Session = Depends(get_db)
):
    """Get billing summary for a specific customer"""
    try:
        billing_service = BillingService(db)
        summary = billing_service.get_customer_billing_summary(customer_id)
        return summary
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error retrieving customer billing summary: {str(e)}")


@router.get("/reports/property-rentals")
async def get_property_rentals_report(
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Rented buildings and land — active property leases."""
    try:
        billing_service = BillingService(db)
        rows = billing_service.get_property_rental_report(company_id=company_id)
        return {
            'count': len(rows),
            'rows': rows,
            'generated_at': date.today().isoformat(),
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error generating property rental report: {str(e)}")


@router.get("/reports/property-rentals/export")
async def export_property_rentals_report(
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """CSV export of rented buildings report."""
    try:
        billing_service = BillingService(db)
        csv_body = billing_service.property_rental_report_csv(company_id=company_id)
        filename = f"property-rentals-{date.today().isoformat()}.csv"
        return PlainTextResponse(
            content=csv_body,
            media_type='text/csv',
            headers={'Content-Disposition': f'attachment; filename="{filename}"'},
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error exporting report: {str(e)}")


@router.get("/reports/property-rentals/schedule")
async def get_property_rentals_schedule(db: Session = Depends(get_db)):
    """Reporting schedule for monthly property rental reports."""
    return get_property_rental_schedule(db)


@router.put("/reports/property-rentals/schedule")
async def update_property_rentals_schedule(
    body: PropertyRentalScheduleUpdate,
    db: Session = Depends(get_db),
):
    """Save reporting schedule (monthly/quarterly rented buildings report)."""
    updates: Dict[str, Any] = body.model_dump(exclude_unset=True)
    return save_property_rental_schedule(db, updates)


@router.post("/reports/property-rentals/run-scheduled")
async def run_scheduled_property_rentals_report(
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Run report now and update schedule last_run / next_run."""
    billing_service = BillingService(db)
    rows = billing_service.get_property_rental_report(company_id=company_id)
    mark_schedule_run(db)
    return {
        'count': len(rows),
        'rows': rows,
        'generated_at': date.today().isoformat(),
        'schedule': get_property_rental_schedule(db),
    }
