from collections.abc import Callable
from dataclasses import replace
from datetime import datetime
from typing import Any

from zbt.core.errors import AppError
from zbt.core.identifiers import new_ulid
from zbt.domains.attribution.models import LeadAttribution, OrderAttribution
from zbt.domains.attribution.repository import AttributionRepository
from zbt.domains.enrollment.models import EnrollmentOrder
from zbt.domains.identity.models import H5User


class AttributionService:
    """第一阶段使用首次有效触点归因，订单创建时固化归因快照。"""

    def __init__(
        self,
        repository: AttributionRepository,
        clock: Callable[[], datetime],
    ) -> None:
        self._repository = repository
        self._clock = clock

    def capture_visit(
        self,
        user: H5User,
        referral_code: str,
    ) -> dict[str, Any]:
        code = self._repository.get_promotion_code(referral_code.strip().upper())
        if code is None or code.status != "ACTIVE":
            raise AppError("REFERRAL_CODE_INVALID", "推广码无效或已停用", 422)
        now = self._clock()
        existing = self._repository.get_lead_by_user(user.id)
        if existing is None:
            lead = LeadAttribution(
                id=new_ulid(),
                h5_user_id=user.id,
                salesperson_id=code.salesperson_id,
                promotion_code_id=code.id,
                source_code=code.code,
                visit_count=1,
                first_touch_at=now,
                last_touch_at=now,
            )
        else:
            lead = replace(
                existing,
                visit_count=existing.visit_count + 1,
                last_touch_at=now,
            )
        self._repository.save_lead(lead)
        return {
            "source_code": lead.source_code,
            "attributed": True,
            "first_touch": existing is None,
        }

    def record_order(self, order: EnrollmentOrder) -> None:
        if self._repository.get_order_attribution(order.id) is not None:
            return
        lead = self._repository.get_lead_by_user(order.user_id)
        if lead is None:
            return
        self._repository.save_order_attribution(
            OrderAttribution(
                id=new_ulid(),
                order_id=order.id,
                h5_user_id=order.user_id,
                salesperson_id=lead.salesperson_id,
                promotion_code_id=lead.promotion_code_id,
                source_code=lead.source_code,
                amount_cents=order.amount_cents,
                attributed_at=self._clock(),
            )
        )

    def performance(self, admin_user_id: str | None = None) -> dict[str, Any]:
        salespersons = [
            item
            for item in self._repository.list_salespersons()
            if item.status == "ACTIVE"
            and (admin_user_id is None or item.admin_user_id == admin_user_id)
        ]
        salesperson_ids = {item.id for item in salespersons}
        leads = [
            item for item in self._repository.list_leads() if item.salesperson_id in salesperson_ids
        ]
        orders = [
            item
            for item in self._repository.list_order_attributions()
            if item.salesperson_id in salesperson_ids
        ]
        items = []
        for salesperson in salespersons:
            owned_leads = [item for item in leads if item.salesperson_id == salesperson.id]
            owned_orders = [item for item in orders if item.salesperson_id == salesperson.id]
            visit_count = sum(item.visit_count for item in owned_leads)
            lead_count = len(owned_leads)
            order_count = len(owned_orders)
            premium_cents = sum(item.amount_cents for item in owned_orders)
            items.append(
                {
                    "salesperson_id": salesperson.id,
                    "admin_user_id": salesperson.admin_user_id,
                    "salesperson_code": salesperson.code,
                    "salesperson_name": salesperson.name,
                    "visit_count": visit_count,
                    "lead_count": lead_count,
                    "order_count": order_count,
                    "premium_cents": premium_cents,
                    "visit_conversion_rate": round(lead_count / visit_count * 100, 1)
                    if visit_count
                    else 0.0,
                    "lead_conversion_rate": round(order_count / lead_count * 100, 1)
                    if lead_count
                    else 0.0,
                    "average_premium_cents": premium_cents // order_count if order_count else 0,
                }
            )
        items.sort(
            key=lambda item: (item["premium_cents"], item["order_count"]),
            reverse=True,
        )
        visit_count = sum(item.visit_count for item in leads)
        lead_count = len(leads)
        order_count = len(orders)
        premium_cents = sum(item.amount_cents for item in orders)
        return {
            "salesperson_count": len(salespersons),
            "visit_count": visit_count,
            "lead_count": lead_count,
            "order_count": order_count,
            "premium_cents": premium_cents,
            "visit_conversion_rate": round(lead_count / visit_count * 100, 1)
            if visit_count
            else 0.0,
            "lead_conversion_rate": round(order_count / lead_count * 100, 1) if lead_count else 0.0,
            "average_premium_cents": premium_cents // order_count if order_count else 0,
            "items": items,
        }

    def order_ids(self, admin_user_id: str | None = None) -> set[str]:
        salesperson_ids = {
            item.id
            for item in self._repository.list_salespersons()
            if admin_user_id is None or item.admin_user_id == admin_user_id
        }
        return {
            item.order_id
            for item in self._repository.list_order_attributions()
            if item.salesperson_id in salesperson_ids
        }

    def list_codes(self, admin_user_id: str | None = None) -> dict[str, Any]:
        salespersons = {
            item.id: item
            for item in self._repository.list_salespersons()
            if admin_user_id is None or item.admin_user_id == admin_user_id
        }
        items = [
            {
                "code": item.code,
                "channel": item.channel,
                "status": item.status,
                "salesperson_id": salesperson.id,
                "salesperson_code": salesperson.code,
                "salesperson_name": salesperson.name,
                "created_at": item.created_at,
            }
            for item in self._repository.list_promotion_codes()
            if (salesperson := salespersons.get(item.salesperson_id)) is not None
        ]
        return {"items": items, "total": len(items)}
