"""Sales router — opportunities, proposals, and contracts."""
from datetime import datetime, timezone
from uuid import UUID
from typing import Optional

import arq
from arq.connections import RedisSettings
from fastapi import APIRouter, Body, Depends, Query
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.config import settings
from src.core.dependencies import require_min_role, require_tenant
from src.core.constants import UserRole
from src.core.schemas.response import success, paginated
from src.database.session import get_db
from src.apps.auth.models.user import User
from src.apps.sales.models.opportunity import Opportunity
from src.apps.sales.schemas.requests import (
    OpportunityCreateRequest,
    OpportunityUpdateRequest,
    OpportunityLostRequest,
    ContractWizardCreateRequest,
    ContractSignRequest,
    ProposalCreateRequest,
    ProposalSendRequest,
)
from src.apps.sales.schemas.responses import (
    OpportunityResponse,
    ContractResponse,
    ContractDetailResponse,
    ProposalResponse,
)
from src.apps.sales.services.opportunity_service import OpportunityService
from src.apps.sales.services.proposal_service import ProposalService
from src.apps.sales.services.contract_service import ContractService

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


# ---------------------------------------------------------------------------
# Opportunities
# ---------------------------------------------------------------------------

@router.get("/opportunities/kanban")
async def get_opportunities_kanban(
    search: Optional[str] = Query(None),
    assigned_to: Optional[UUID] = Query(None),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    kanban = await OpportunityService.get_kanban(db, tenant_id, search=search, assigned_to=assigned_to)
    return success(kanban)


@router.get("/opportunities")
async def list_opportunities(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    stage: Optional[str] = Query(None),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    filters = [
        Opportunity.tenant_id == tenant_id,
        Opportunity.deleted_at.is_(None),
    ]
    if stage:
        filters.append(Opportunity.stage == stage)

    total = (
        await db.execute(select(func.count()).select_from(Opportunity).where(*filters))
    ).scalar_one()
    offset = (page - 1) * page_size
    result = await db.execute(
        select(Opportunity)
        .where(*filters)
        .order_by(Opportunity.created_at.desc())
        .offset(offset)
        .limit(page_size)
    )
    items = []
    now = datetime.now(timezone.utc)
    for opp in result.scalars().all():
        data = OpportunityResponse.model_validate(opp).model_dump()
        if opp.stage_entered_at:
            entered = opp.stage_entered_at
            if entered.tzinfo is None:
                entered = entered.replace(tzinfo=timezone.utc)
            data["days_in_stage"] = (now - entered).days
        items.append(data)
    return paginated(items, total, page, page_size)


@router.get("/opportunities/{opportunity_id}")
async def get_opportunity(
    opportunity_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    opp = await OpportunityService.get_by_id(db, tenant_id, opportunity_id)
    data = OpportunityResponse.model_validate(opp).model_dump()
    if opp.stage_entered_at:
        now = datetime.now(timezone.utc)
        entered = opp.stage_entered_at
        if entered.tzinfo is None:
            entered = entered.replace(tzinfo=timezone.utc)
        data["days_in_stage"] = (now - entered).days
    return success(data)


@router.post("/opportunities", status_code=201)
async def create_opportunity(
    body: OpportunityCreateRequest,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    opp = await OpportunityService.create(db, tenant_id, body.model_dump(), current_user)
    data = OpportunityResponse.model_validate(opp).model_dump()
    data["days_in_stage"] = 0
    return success(data, "Opportunity created")


@router.patch("/opportunities/{opportunity_id}")
async def update_opportunity(
    opportunity_id: UUID,
    body: OpportunityUpdateRequest,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    opp = await OpportunityService.update(
        db, tenant_id, opportunity_id, body.model_dump(exclude_unset=True), current_user
    )
    data = OpportunityResponse.model_validate(opp).model_dump()
    if opp.stage_entered_at:
        now = datetime.now(timezone.utc)
        entered = opp.stage_entered_at
        if entered.tzinfo is None:
            entered = entered.replace(tzinfo=timezone.utc)
        data["days_in_stage"] = (now - entered).days
    return success(data)


@router.delete("/opportunities/{opportunity_id}")
async def delete_opportunity(
    opportunity_id: UUID,
    body: OpportunityLostRequest = Body(default_factory=OpportunityLostRequest),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    await OpportunityService.soft_delete(
        db, tenant_id, opportunity_id, body.lost_reason, current_user
    )
    return success(None, "Opportunity marked as lost")


# ---------------------------------------------------------------------------
# Proposals
# ---------------------------------------------------------------------------

@router.post("/proposals", status_code=201)
async def create_proposal(
    body: ProposalCreateRequest,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    data = body.model_dump()
    # Convert line item objects to plain dicts for the service
    data["line_items"] = [li.model_dump() for li in body.line_items]
    proposal = await ProposalService.create(db, tenant_id, data, current_user)
    return success(ProposalResponse.model_validate(proposal), "Proposal created")


@router.get("/proposals/{proposal_id}")
async def get_proposal(
    proposal_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    proposal = await ProposalService.get_by_id(db, tenant_id, proposal_id)
    return success(ProposalResponse.model_validate(proposal))


@router.post("/proposals/{proposal_id}/send")
async def send_proposal(
    proposal_id: UUID,
    body: ProposalSendRequest = Body(default_factory=ProposalSendRequest),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    proposal = await ProposalService.send(db, tenant_id, proposal_id, current_user)
    return success(ProposalResponse.model_validate(proposal), "Proposal sent")


@router.post("/proposals/{proposal_id}/accept")
async def accept_proposal(
    proposal_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    proposal = await ProposalService.mark_accepted(db, tenant_id, proposal_id, current_user)
    return success(ProposalResponse.model_validate(proposal), "Proposal accepted")


# ---------------------------------------------------------------------------
# Contracts
# ---------------------------------------------------------------------------

@router.get("/contracts")
async def list_contracts(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    status: Optional[str] = Query(None),
    search: Optional[str] = Query(None),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    items, total = await ContractService.list(
        db, tenant_id, page=page, page_size=page_size, status=status, search=search
    )
    # Use list-safe variant: strips purchaser_signature_b64 and raw pdf_s3_key
    data = [ContractResponse.from_orm_list(c) for c in items]
    return paginated(data, total, page, page_size)


@router.get("/contracts/{contract_id}")
async def get_contract(
    contract_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    contract = await ContractService.get_by_id(db, tenant_id, contract_id)
    # ContractDetailResponse includes purchaser_signature_b64 (single-record view only)
    detail = ContractDetailResponse.model_validate(contract)
    detail.pdf_available = bool(getattr(contract, "pdf_s3_key", None))
    return success(detail)


@router.post("/contracts", status_code=201)
async def create_contract(
    body: ContractWizardCreateRequest,
    current_user: User = Depends(require_min_role(UserRole.MANAGER)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    data = body.model_dump()
    data["line_items"] = [li.model_dump() for li in body.line_items]
    contract = await ContractService.create(db, tenant_id, data, current_user)
    resp = ContractResponse.from_orm_list(contract)
    return success(resp, "Contract created")


@router.post("/contracts/{contract_id}/sign")
async def sign_contract(
    contract_id: UUID,
    body: ContractSignRequest,
    current_user: User = Depends(require_min_role(UserRole.MANAGER)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    arq_redis = None
    try:
        arq_redis = await arq.create_pool(RedisSettings.from_dsn(settings.REDIS_URL))
    except Exception:
        pass
    try:
        contract = await ContractService.sign(
            db,
            tenant_id,
            contract_id,
            signature_b64=body.purchaser_signature_b64,
            witness_name=body.witness_name,
            current_user=current_user,
            arq_redis=arq_redis,
        )
    finally:
        if arq_redis is not None:
            await arq_redis.aclose()
    detail = ContractDetailResponse.model_validate(contract)
    detail.pdf_available = bool(getattr(contract, "pdf_s3_key", None))
    return success(detail, "Contract signed")


@router.delete("/contracts/{contract_id}")
async def delete_contract(
    contract_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    await ContractService.soft_delete(db, tenant_id, contract_id, current_user)
    return success(None, "Contract deleted")


@router.get("/contracts/{contract_id}/pdf")
async def get_contract_pdf_url(
    contract_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    """Return a short-lived presigned S3 URL for the contract PDF (expires in 600 s)."""
    import asyncio
    from fastapi import HTTPException
    import boto3
    from botocore.exceptions import NoCredentialsError, ClientError

    contract = await ContractService.get_by_id(db, tenant_id, contract_id)

    if not contract.pdf_s3_key:
        raise HTTPException(status_code=404, detail="PDF not yet generated")

    if not settings.AWS_ACCESS_KEY_ID or not settings.AWS_SECRET_ACCESS_KEY:
        raise HTTPException(
            status_code=503,
            detail="PDF download unavailable",
        )

    s3_key = contract.pdf_s3_key  # capture before leaving async context

    def _make_presigned() -> str:
        s3 = boto3.client(
            "s3",
            region_name=settings.AWS_REGION,
            aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
            aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
        )
        return s3.generate_presigned_url(
            "get_object",
            Params={"Bucket": settings.S3_BUCKET, "Key": s3_key},
            ExpiresIn=600,
        )

    try:
        presigned_url = await asyncio.to_thread(_make_presigned)
    except (NoCredentialsError, ClientError):
        raise HTTPException(
            status_code=503,
            detail="PDF download unavailable",
        )

    return success({"url": presigned_url, "expires_in": 600})


# ---------------------------------------------------------------------------
# Fee Items (for wizard step 4 — fee catalog)
# ---------------------------------------------------------------------------

@router.get("/fee-items")
async def list_fee_items(
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    from src.apps.settings.models.fee_item import FeeItem as FeeItemModel
    result = await db.execute(
        select(FeeItemModel).where(
            FeeItemModel.tenant_id == tenant_id,
            FeeItemModel.is_active.is_(True),
        ).order_by(FeeItemModel.category, FeeItemModel.name)
    )
    items = result.scalars().all()
    return success([{
        "id": str(fi.id),
        "name": fi.name,
        "category": fi.category,
        "description": getattr(fi, "description", None),
        "unit_price": str(fi.unit_price),
        "is_taxable": getattr(fi, "is_taxable", True),
        "is_active": fi.is_active,
    } for fi in items])
