from datetime import datetime, timezone

from fastapi import HTTPException, status
from sqlalchemy.orm import Session

from ..accounts.models import Account
from ..contacts.models import Contact
from ..opportunities.models import Opportunity
from ..pipelines.models import Pipeline, PipelineStage
from .dto import LeadConvert, LeadCreate, LeadUpdate
from .models import Lead


def _validate_pipeline_stage(
    db: Session,
    pipeline_id: int | None,
    stage_id: int | None,
    tenant_id: int,
    company_id: int,
) -> None:
    """Valida que embudo/etapa existan en el tenant/company y sean coherentes.

    Evita enlazar la oportunidad convertida a un embudo/etapa de otro tenant
    (fuga multi-tenant) o a un id inexistente (que produciría un 500).
    """
    if pipeline_id is not None:
        exists = (
            db.query(Pipeline.id)
            .filter(
                Pipeline.id == pipeline_id,
                Pipeline.tenant_id == tenant_id,
                Pipeline.company_id == company_id,
                Pipeline.deleted_at.is_(None),
            )
            .first()
        )
        if not exists:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="El embudo indicado no existe",
            )
    if stage_id is not None:
        stage = (
            db.query(PipelineStage)
            .filter(
                PipelineStage.id == stage_id,
                PipelineStage.tenant_id == tenant_id,
                PipelineStage.company_id == company_id,
                PipelineStage.deleted_at.is_(None),
            )
            .first()
        )
        if not stage:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="La etapa indicada no existe",
            )
        if pipeline_id is not None and stage.pipeline_id != pipeline_id:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="La etapa no pertenece al embudo indicado",
            )


def get_leads(
    db: Session,
    tenant_id: int,
    company_id: int,
    search: str | None = None,
    lead_status: str | None = None,
) -> list[Lead]:
    query = db.query(Lead).filter(
        Lead.tenant_id == tenant_id,
        Lead.company_id == company_id,
        Lead.deleted_at.is_(None),
    )
    if lead_status:
        query = query.filter(Lead.status == lead_status)
    if search:
        pattern = f"%{search}%"
        query = query.filter(
            Lead.name.ilike(pattern)
            | Lead.company_name.ilike(pattern)
            | Lead.email.ilike(pattern)
        )
    return query.order_by(Lead.created_at.desc()).all()


def get_lead(db: Session, lead_id: int, tenant_id: int, company_id: int) -> Lead:
    lead = (
        db.query(Lead)
        .filter(
            Lead.id == lead_id,
            Lead.tenant_id == tenant_id,
            Lead.company_id == company_id,
            Lead.deleted_at.is_(None),
        )
        .first()
    )
    if not lead:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Prospecto no encontrado")
    return lead


def create_lead(db: Session, payload: LeadCreate, tenant_id: int, company_id: int) -> Lead:
    lead = Lead(**payload.model_dump(), tenant_id=tenant_id, company_id=company_id)
    db.add(lead)
    db.commit()
    db.refresh(lead)
    return lead


def update_lead(db: Session, lead_id: int, payload: LeadUpdate, tenant_id: int, company_id: int) -> Lead:
    lead = get_lead(db, lead_id, tenant_id, company_id)
    for field, value in payload.model_dump(exclude_unset=True).items():
        setattr(lead, field, value)
    db.commit()
    db.refresh(lead)
    return lead


def delete_lead(db: Session, lead_id: int, tenant_id: int, company_id: int) -> None:
    lead = get_lead(db, lead_id, tenant_id, company_id)
    lead.deleted_at = datetime.now(timezone.utc)
    db.commit()


def convert_lead(
    db: Session, lead_id: int, payload: LeadConvert, tenant_id: int, company_id: int
) -> dict:
    """Convierte un prospecto en cuenta (+ contacto y oportunidad opcionales).

    Es idempotente: si el prospecto ya fue convertido, retorna las referencias existentes.
    """
    lead = get_lead(db, lead_id, tenant_id, company_id)
    if lead.status == "converted" and lead.converted_account_id:
        return {
            "lead": lead,
            "account_id": lead.converted_account_id,
            "contact_id": lead.converted_contact_id,
            "opportunity_id": lead.converted_opportunity_id,
        }

    # 1. Cuenta a partir de la empresa (o nombre) del prospecto
    account = Account(
        name=lead.company_name or lead.name,
        email=lead.email,
        phone=lead.phone,
        status="active",
        owner_user_id=lead.owner_user_id,
        tenant_id=tenant_id,
        company_id=company_id,
    )
    db.add(account)
    db.flush()  # necesitamos el id de la cuenta para enlazar contacto/oportunidad

    # 2. Contacto (si el prospecto trae nombre de contacto no vacío)
    contact = None
    contact_name = (lead.contact_name or "").strip()
    if contact_name:
        parts = contact_name.split(" ", 1)
        contact = Contact(
            account_id=account.id,
            first_name=parts[0],
            last_name=parts[1] if len(parts) > 1 else None,
            email=lead.email,
            phone=lead.phone,
            is_primary=True,
            owner_user_id=lead.owner_user_id,
            tenant_id=tenant_id,
            company_id=company_id,
        )
        db.add(contact)
        db.flush()

    # 3. Oportunidad (opcional)
    opportunity = None
    if payload.create_opportunity:
        # Valida embudo/etapa en el scope del tenant/company antes de enlazar
        _validate_pipeline_stage(db, payload.pipeline_id, payload.stage_id, tenant_id, company_id)
        opportunity = Opportunity(
            name=payload.opportunity_name or lead.name,
            account_id=account.id,
            contact_id=contact.id if contact else None,
            pipeline_id=payload.pipeline_id,
            stage_id=payload.stage_id,
            amount=payload.amount if payload.amount is not None else lead.estimated_value,
            source=lead.source,
            status="open",
            owner_user_id=lead.owner_user_id,
            tenant_id=tenant_id,
            company_id=company_id,
        )
        db.add(opportunity)
        db.flush()

    # 4. Marcar el prospecto como convertido y enlazar
    lead.status = "converted"
    lead.converted_account_id = account.id
    lead.converted_contact_id = contact.id if contact else None
    lead.converted_opportunity_id = opportunity.id if opportunity else None

    db.commit()
    db.refresh(lead)
    return {
        "lead": lead,
        "account_id": account.id,
        "contact_id": contact.id if contact else None,
        "opportunity_id": opportunity.id if opportunity else None,
    }
