"""integrations domain — business logic."""
from __future__ import annotations

import secrets
from typing import Optional
from uuid import UUID

import structlog

from src.core.exceptions import ConflictError, NotFoundError
from src.core.security.encryption import decrypt_value, encrypt_value
from src.domain.connectors.repository import ConnectorRepository
from src.domain.connectors.service import ConnectorService
from src.domain.edge_functions.repository import ProjectWebhookEndpointRepository
from src.domain.integrations.models import ProjectIntegration
from src.domain.integrations.providers import (
    ProviderDef,
    get_provider,
    supported_providers,
    supports_webhooks,
)
from src.domain.integrations.repository import ProjectIntegrationRepository
from src.domain.integrations.schemas import (
    InstallIntegrationRequest,
    IntegrationResponse,
    ProviderInfoResponse,
    UpdateIntegrationSecretsRequest,
)
from src.domain.projects.repository import ProjectRepository
from src.integrations.cloud_control_client import CloudControlClient

logger = structlog.get_logger()


class IntegrationsService:
    def __init__(
        self,
        *,
        integration_repo: ProjectIntegrationRepository,
        webhook_repo: ProjectWebhookEndpointRepository,
        cloud_client: CloudControlClient,
        connector_repo: Optional[ConnectorRepository] = None,
        project_repo: Optional[ProjectRepository] = None,
    ) -> None:
        self._repo = integration_repo
        self._wh = webhook_repo
        self._cloud = cloud_client
        self._connector_repo = connector_repo
        self._project_repo = project_repo

    # ------------------------------------------------------------------
    # Provider catalog
    # ------------------------------------------------------------------

    def list_providers(self) -> list[ProviderInfoResponse]:
        from src.domain.integrations.providers import PROVIDERS
        return [
            ProviderInfoResponse(
                name=p.name,
                display_name=p.display_name,
                config_fields=list(p.config_fields),
                secret_fields=list(p.secret_fields),
                supports_webhooks=p.webhook_events is not None,
                webhook_events=list(p.webhook_events) if p.webhook_events else None,
            )
            for p in PROVIDERS.values()
        ]

    # ------------------------------------------------------------------
    # Integration management
    # ------------------------------------------------------------------

    async def list_integrations(self, project_id: UUID) -> list[IntegrationResponse]:
        rows = await self._repo.list_for_project(project_id)
        return [_integration_response(r) for r in rows]

    async def get_integration(
        self, project_id: UUID, provider: str
    ) -> IntegrationResponse:
        _require_known_provider(provider)
        row = await self._repo.get_by_provider(project_id, provider)
        if not row:
            raise NotFoundError(f"Integration {provider!r} not installed")
        return _integration_response(row)

    async def install(
        self, project_id: UUID, provider: str, body: InstallIntegrationRequest
    ) -> IntegrationResponse:
        pdef = _require_known_provider(provider)

        # Encrypt incoming secrets
        secrets_enc: dict = {}
        if body.secrets:
            for key, val in body.secrets.items():
                if key not in pdef.secret_fields:
                    raise ConflictError(
                        f"Unknown secret field {key!r} for provider {provider!r}"
                    )
                secrets_enc[key] = encrypt_value(val)

        # Merge with existing secrets (don't wipe secrets not re-submitted)
        existing = await self._repo.get_by_provider(project_id, provider)
        if existing and existing.secrets_enc:
            merged_secrets = dict(existing.secrets_enc)
            merged_secrets.update(secrets_enc)
            secrets_enc = merged_secrets

        row = await self._repo.upsert(
            project_id=project_id,
            provider=provider,
            status="active",
            config_json=body.config,
            secrets_enc=secrets_enc or None,
        )

        # Auto-register webhook endpoint if not yet done and provider supports it
        if supports_webhooks(provider) and not row.webhook_auto_registered:
            await self._ensure_webhook_endpoint(
                project_id=project_id,
                provider=provider,
                events=body.events or (list(pdef.webhook_events) if pdef.webhook_events else []),
                function_slug=body.function_slug,
            )
            row = await self._repo.update(row, webhook_auto_registered=True)

        await self._push_to_cloud(project_id, row)
        logger.info("integration_installed", project_id=str(project_id), provider=provider)
        return _integration_response(row)

    async def uninstall(self, project_id: UUID, provider: str) -> None:
        _require_known_provider(provider)
        row = await self._repo.get_by_provider(project_id, provider)
        if not row:
            raise NotFoundError(f"Integration {provider!r} not installed")

        row = await self._repo.update(row, status="disconnected")

        # Deactivate webhook endpoint
        wh = await self._wh.get_by_provider(project_id, provider)
        if wh:
            await self._wh.update(wh, is_active=False)

        await self._push_to_cloud(project_id, row)
        logger.info("integration_uninstalled", project_id=str(project_id), provider=provider)

    async def rotate_secrets(
        self,
        project_id: UUID,
        provider: str,
        body: UpdateIntegrationSecretsRequest,
    ) -> IntegrationResponse:
        pdef = _require_known_provider(provider)
        row = await self._repo.get_by_provider(project_id, provider)
        if not row:
            raise NotFoundError(f"Integration {provider!r} not installed")

        existing_enc = dict(row.secrets_enc or {})
        for key, val in body.secrets.items():
            if key not in pdef.secret_fields:
                raise ConflictError(
                    f"Unknown secret field {key!r} for provider {provider!r}"
                )
            existing_enc[key] = encrypt_value(val)

        row = await self._repo.update(row, secrets_enc=existing_enc)
        await self._push_to_cloud(project_id, row)
        logger.info("integration_secrets_rotated", project_id=str(project_id), provider=provider)
        return _integration_response(row)

    # ------------------------------------------------------------------
    # Internal
    # ------------------------------------------------------------------

    async def _ensure_webhook_endpoint(
        self,
        project_id: UUID,
        provider: str,
        events: list[str],
        function_slug: Optional[str],
    ) -> None:
        """Create a webhook endpoint row for auto-registered providers.

        The actual provider API call (Stripe /v1/webhook_endpoints, etc.) is
        handled by a background arq task — not invoked here.
        """
        existing = await self._wh.get_by_provider(project_id, provider)
        if existing and existing.is_active:
            return  # Already registered

        wh_token = secrets.token_urlsafe(32)
        await self._wh.create(
            project_id=project_id,
            wh_token=wh_token,
            provider=provider,
            events_json=events,
            function_slug=function_slug,
            auto_registered=True,
        )
        logger.info(
            "webhook_endpoint_created",
            project_id=str(project_id),
            provider=provider,
            token_prefix=wh_token[:8],
        )

    async def _push_to_cloud(
        self, project_id: UUID, row: ProjectIntegration
    ) -> None:
        """Push integration config (no raw secrets in D1) and decrypted secrets to Cloud KV."""
        # secret_ref is an opaque identifier Cloud uses as the AUTH_KV handle
        secret_ref = f"{project_id}:{row.provider}" if row.secrets_enc else None
        await self._cloud.upsert_integration(
            project_id=str(project_id),
            provider=row.provider,
            status=row.status,
            secret_ref=secret_ref,
            config=row.config_json or {},
        )
        if row.status == "active" and secret_ref and row.secrets_enc:
            decrypted = _decrypt_secret_bag(row.secrets_enc)
            if decrypted:
                await self._cloud.push_integration_secrets(
                    project_id=str(project_id),
                    provider=row.provider,
                    secret_ref=secret_ref,
                    secrets=decrypted,
                )
        else:
            await self._cloud.delete_integration_secrets(
                project_id=str(project_id),
                provider=row.provider,
            )
        if self._connector_repo and self._project_repo:
            project = await self._project_repo.get_by_id(project_id)
            if project:
                await ConnectorService(
                    repo=self._connector_repo,
                    cloud_client=self._cloud,
                ).upsert_from_project_integration(
                    project_id=project_id,
                    workspace_id=project.workspace_id,
                    provider=row.provider,
                    status=row.status,
                    config=row.config_json or {},
                    secrets_enc=row.secrets_enc,
                )


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _decrypt_secret_bag(secrets_enc: dict) -> dict[str, str]:
    out: dict[str, str] = {}
    for key, value in secrets_enc.items():
        if not isinstance(value, str) or not value:
            continue
        try:
            out[str(key)] = decrypt_value(value)
        except Exception:
            logger.warning("integration_secret_decrypt_failed", field=str(key))
    return out


def _require_known_provider(provider: str) -> ProviderDef:
    pdef = get_provider(provider)
    if not pdef:
        raise NotFoundError(
            f"Unknown provider {provider!r}. Supported: {', '.join(supported_providers())}"
        )
    return pdef


def _integration_response(row: ProjectIntegration) -> IntegrationResponse:
    secrets_configured = list(row.secrets_enc.keys()) if row.secrets_enc else []
    return IntegrationResponse(
        id=row.id,
        project_id=row.project_id,
        provider=row.provider,
        status=row.status,
        config=row.config_json,
        secrets_configured=secrets_configured,
        webhook_auto_registered=row.webhook_auto_registered,
        installed_at=row.installed_at,
        updated_at=row.updated_at,
    )
