"""Lead application service — Phase 6.1."""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import UUID, uuid4

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import InMemoryEventPublisher, get_event_publisher
from app.events.types import CrmEventType
from app.models.foundation import Lead
from app.models.supporting import LeadAssignment
from app.models.types import AuditAction, LeadStatus
from app.repositories.foundation import LeadRepository
from app.repositories.supporting import LeadAssignmentRepository
from app.schemas.foundation import LeadAssignRequest, LeadCreate, LeadUpdate
from app.services.audit_service import AuditService
from app.validators import (
    validate_amount,
    validate_email,
    validate_lead_status,
    validate_non_empty,
    validate_owner,
    validate_phone,
)
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


def _apply_update(entity, data: dict) -> None:
    for key, value in data.items():
        setattr(entity, key, value)


def _compose_full_name(first: str, last: str, full: str | None) -> str:
    if full and full.strip():
        return full.strip()
    joined = f"{first} {last}".strip()
    return joined or "Unnamed Lead"


def _next_lead_number() -> str:
    return f"LEAD-{uuid4().hex[:10].upper()}"


class LeadService:
    def __init__(
        self,
        session: AsyncSession,
        publisher: InMemoryEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = LeadRepository(session)
        self.assignment_repo = LeadAssignmentRepository(session)
        self.audit = AuditService(session)
        self.publisher = publisher or get_event_publisher()

    async def create(
        self,
        tenant_id: UUID,
        body: LeadCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> Lead:
        actor_id = actor.user_id if actor else None
        owner = validate_owner(body.owner_user_id or actor_id or "system")
        first = (body.first_name or "").strip()
        last = (body.last_name or "").strip()
        full_name = _compose_full_name(first, last, body.full_name or body.title)
        email = validate_email(body.email)
        mobile = validate_phone(body.mobile, field="mobile")
        phone = validate_phone(body.phone, field="phone")
        status = validate_lead_status(body.status)
        estimated = validate_amount(body.estimated_value)
        lead_number = (body.lead_number or "").strip() or _next_lead_number()
        if await self.repo.get_by_lead_number(tenant_id, lead_number) is not None:
            raise AppError(
                "شماره سرنخ تکراری است",
                status_code=409,
                error_code="lead_number_exists",
            )
        company = body.company or body.company_name
        entity = Lead(
            tenant_id=tenant_id,
            lead_number=lead_number,
            first_name=first,
            last_name=last,
            full_name=full_name,
            title=body.title or full_name,
            company=company,
            company_name=company,
            job_title=body.job_title,
            email=email,
            mobile=mobile,
            phone=phone,
            website=body.website,
            lead_source_id=body.lead_source_id,
            lead_status_id=body.lead_status_id,
            status=status,
            source=body.source,
            assigned_user_id=body.assigned_user_id or owner,
            priority=body.priority,
            estimated_value=estimated,
            expected_close_date=body.expected_close_date,
            description=body.description,
            owner_user_id=owner,
            contact_id=body.contact_id,
            organization_id=body.organization_id,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="lead",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id,
            message=f"Lead {entity.lead_number} created",
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=CrmEventType.LEAD_CREATED,
            aggregate_type="lead",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={
                "lead_number": entity.lead_number,
                "status": entity.status.value,
                "full_name": entity.full_name,
            },
        )
        return entity

    async def update(
        self,
        tenant_id: UUID,
        lead_id: UUID,
        body: LeadUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> Lead:
        entity = await self.repo.get(tenant_id, lead_id)
        if entity is None:
            raise NotFoundError("سرنخ یافت نشد", error_code="lead_not_found")
        actor_id = actor.user_id if actor else None
        data = body.model_dump(exclude_unset=True)
        changes: dict = {}
        old_status = entity.status
        if "email" in data:
            data["email"] = validate_email(data["email"])
        if "mobile" in data:
            data["mobile"] = validate_phone(data["mobile"], field="mobile")
        if "phone" in data:
            data["phone"] = validate_phone(data["phone"], field="phone")
        if "status" in data and data["status"] is not None:
            data["status"] = validate_lead_status(data["status"])
        if "estimated_value" in data and data["estimated_value"] is not None:
            data["estimated_value"] = validate_amount(data["estimated_value"])
        if "owner_user_id" in data and data["owner_user_id"] is not None:
            data["owner_user_id"] = validate_owner(data["owner_user_id"])
        if "first_name" in data or "last_name" in data or "full_name" in data:
            first = data.get("first_name", entity.first_name)
            last = data.get("last_name", entity.last_name)
            data["full_name"] = _compose_full_name(
                first or "", last or "", data.get("full_name")
            )
        for key, value in data.items():
            old = getattr(entity, key)
            if old != value:
                changes[key] = {"from": str(old), "to": str(value)}
        _apply_update(entity, data)
        entity.updated_by = actor_id
        action = AuditAction.UPDATE
        if "status" in changes:
            action = AuditAction.STATUS_CHANGE
        elif changes:
            action = AuditAction.FIELD_CHANGE
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="lead",
            entity_id=entity.id,
            action=action,
            actor_user_id=actor_id,
            changes=changes or None,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=CrmEventType.LEAD_UPDATED,
            aggregate_type="lead",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={
                "status": entity.status.value,
                "previous_status": old_status.value,
            },
        )
        return entity

    async def assign(
        self,
        tenant_id: UUID,
        lead_id: UUID,
        body: LeadAssignRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> Lead:
        entity = await self.repo.get(tenant_id, lead_id)
        if entity is None:
            raise NotFoundError("سرنخ یافت نشد", error_code="lead_not_found")
        actor_id = actor.user_id if actor else None
        assigned = validate_non_empty(body.assigned_user_id, field="assigned_user_id")
        await self.assignment_repo.clear_current(tenant_id, lead_id)
        assignment = LeadAssignment(
            tenant_id=tenant_id,
            lead_id=lead_id,
            assigned_user_id=assigned,
            assigned_by=actor_id,
            assigned_at=datetime.now(timezone.utc),
            note=body.note,
            is_current=True,
        )
        await self.assignment_repo.add(assignment)
        entity.assigned_user_id = assigned
        entity.updated_by = actor_id
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="lead",
            entity_id=entity.id,
            action=AuditAction.ASSIGNMENT,
            actor_user_id=actor_id,
            changes={"assigned_user_id": assigned},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def soft_delete(
        self,
        tenant_id: UUID,
        lead_id: UUID,
        *,
        actor: CurrentUser | None = None,
    ) -> Lead:
        entity = await self.repo.get(tenant_id, lead_id)
        if entity is None:
            raise NotFoundError("سرنخ یافت نشد", error_code="lead_not_found")
        actor_id = actor.user_id if actor else None
        await self.repo.soft_delete(entity, deleted_by=actor_id)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="lead",
            entity_id=entity.id,
            action=AuditAction.DELETE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def restore(
        self,
        tenant_id: UUID,
        lead_id: UUID,
        *,
        actor: CurrentUser | None = None,
    ) -> Lead:
        entity = await self.repo.get_including_deleted(tenant_id, lead_id)
        if entity is None:
            raise NotFoundError("سرنخ یافت نشد", error_code="lead_not_found")
        actor_id = actor.user_id if actor else None
        await self.repo.restore(entity)
        entity.updated_by = actor_id
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="lead",
            entity_id=entity.id,
            action=AuditAction.RESTORE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, lead_id: UUID) -> Lead:
        entity = await self.repo.get(tenant_id, lead_id)
        if entity is None:
            raise NotFoundError("سرنخ یافت نشد", error_code="lead_not_found")
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)
