"""Provider registry & lifecycle services."""
from __future__ import annotations

from datetime import datetime, timezone
from decimal import Decimal
from typing import Any
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.events.publisher import get_event_publisher
from app.events.types import CommunicationEventType
from app.models.foundation import ProviderConfig, SenderNumber
from app.models.types import ProviderLifecycleStatus
from app.providers import get_provider
from app.repositories.foundation import ProviderConfigRepository, SenderNumberRepository
from app.services.audit_service import AuditService
from app.validators import validate_provider_payload, validate_sender_number
from shared.exceptions import NotFoundError


class ProviderService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = ProviderConfigRepository(session)
        self.senders = SenderNumberRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()

    async def create(
        self, tenant_id: UUID, data: dict[str, Any], *, actor_id: str | None = None
    ) -> ProviderConfig:
        validate_provider_payload(data)
        entity = ProviderConfig(
            tenant_id=tenant_id,
            name=data["name"],
            provider_kind=data["provider_kind"],
            channel=data["channel"],
            priority=data.get("priority", 100),
            credentials=data.get("credentials"),
            settings=data.get("settings"),
            balance=data.get("balance"),
            rate_limit_per_minute=data.get("rate_limit_per_minute"),
            max_retries=data.get("max_retries", 3),
            is_default=data.get("is_default", False),
            status=ProviderLifecycleStatus.ACTIVE.value,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.log(
            tenant_id=tenant_id,
            entity_type="provider_config",
            entity_id=entity.id,
            action="create",
            actor_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def update(
        self,
        tenant_id: UUID,
        provider_id: UUID,
        data: dict[str, Any],
        *,
        actor_id: str | None = None,
    ) -> ProviderConfig:
        entity = await self.repo.get(tenant_id, provider_id)
        if entity is None:
            raise NotFoundError("Provider not found")
        for key in (
            "name",
            "priority",
            "credentials",
            "settings",
            "balance",
            "rate_limit_per_minute",
            "max_retries",
            "status",
            "is_default",
        ):
            if key in data and data[key] is not None:
                setattr(entity, key, data[key])
        entity.updated_by = actor_id
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list(self, tenant_id: UUID) -> list[ProviderConfig]:
        return list(await self.repo.list_all_status(tenant_id))

    async def get(self, tenant_id: UUID, provider_id: UUID) -> ProviderConfig:
        entity = await self.repo.get(tenant_id, provider_id)
        if entity is None:
            raise NotFoundError("Provider not found")
        return entity

    async def delete(
        self, tenant_id: UUID, provider_id: UUID, *, actor_id: str | None = None
    ) -> None:
        entity = await self.get(tenant_id, provider_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id)
        await self.session.commit()

    async def get_balance(self, tenant_id: UUID, provider_id: UUID) -> Decimal | None:
        entity = await self.get(tenant_id, provider_id)
        adapter = get_provider(entity.provider_kind)
        result = await adapter.get_balance(entity.credentials or {})
        if result.balance is not None:
            entity.balance = Decimal(str(result.balance))
            await self.session.commit()
        return entity.balance

    async def health_probe(self, tenant_id: UUID, provider_id: UUID) -> dict[str, Any]:
        entity = await self.get(tenant_id, provider_id)
        adapter = get_provider(entity.provider_kind)
        health = await adapter.health_check(entity.credentials or {})
        entity.last_health_at = datetime.now(timezone.utc)
        if not health.healthy:
            entity.status = ProviderLifecycleStatus.DEGRADED.value
            entity.last_error = health.detail
        elif entity.circuit_state != "open":
            entity.status = ProviderLifecycleStatus.ACTIVE.value
            entity.last_error = None
        await self.session.commit()
        return {
            "healthy": health.healthy,
            "detail": health.detail,
            "status": entity.status,
            "circuit_state": entity.circuit_state,
        }

    async def record_success(self, entity: ProviderConfig) -> None:
        entity.consecutive_failures = 0
        if entity.circuit_state != "closed":
            entity.circuit_state = "closed"
            entity.circuit_opened_at = None
            entity.status = ProviderLifecycleStatus.ACTIVE.value
            self.events.publish(
                event_type=CommunicationEventType.PROVIDER_CIRCUIT_CLOSED,
                aggregate_type="provider_config",
                aggregate_id=entity.id,
                tenant_id=entity.tenant_id,
            )
        entity.last_health_at = datetime.now(timezone.utc)
        await self.session.flush()

    async def record_failure(self, entity: ProviderConfig, error: str) -> None:
        entity.consecutive_failures += 1
        entity.last_error = error
        entity.last_health_at = datetime.now(timezone.utc)
        threshold = settings.circuit_breaker_failure_threshold
        if entity.consecutive_failures >= threshold:
            entity.circuit_state = "open"
            entity.circuit_opened_at = datetime.now(timezone.utc)
            entity.status = ProviderLifecycleStatus.CIRCUIT_OPEN.value
            self.events.publish(
                event_type=CommunicationEventType.PROVIDER_CIRCUIT_OPENED,
                aggregate_type="provider_config",
                aggregate_id=entity.id,
                tenant_id=entity.tenant_id,
                payload={"error": error},
            )
        else:
            entity.status = ProviderLifecycleStatus.DEGRADED.value
        await self.session.flush()

    def is_circuit_allowing(self, entity: ProviderConfig) -> bool:
        if entity.circuit_state != "open":
            return True
        opened = entity.circuit_opened_at
        if opened is None:
            return False
        if opened.tzinfo is None:
            opened = opened.replace(tzinfo=timezone.utc)
        elapsed = (datetime.now(timezone.utc) - opened).total_seconds()
        if elapsed >= settings.circuit_breaker_reset_seconds:
            entity.circuit_state = "half_open"
            return True
        return False

    async def create_sender(
        self, tenant_id: UUID, data: dict[str, Any], *, actor_id: str | None = None
    ) -> SenderNumber:
        validate_sender_number(data)
        entity = SenderNumber(
            tenant_id=tenant_id,
            channel=data["channel"],
            value=data["value"],
            label=data.get("label"),
            provider_config_id=data.get("provider_config_id"),
            is_default=data.get("is_default", False),
            is_active=data.get("is_active", True),
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.senders.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_senders(self, tenant_id: UUID) -> list[SenderNumber]:
        return list(await self.senders.list_by_tenant(tenant_id))
