import urllib.parse
from typing import Protocol

from src.__types.errors import NotificationError
from src.__types.index import Distribution, Tenant
from src.domain.entities.contract_event import ContractEvent
from src.domain.entities.quota_allocation import QuotaAllocation
from src.infrastructure.clients.sns_client import SnsClient
from src.logger import logger


class ContractEventNotifierProtocol(Protocol):
    def notify_tenant_created(self, tenant: Tenant, contract_event: ContractEvent) -> None: ...

    def notify_tenant_creation_failed(self, contract_event: ContractEvent, error_message: str) -> None: ...

    def notify_quota_allocation_added(self, tenant: Tenant, allocation: QuotaAllocation, contract_id: str) -> None: ...

    def notify_quota_allocation_failed(
        self,
        tenant: Tenant,
        contract_event: ContractEvent,
        error_message: str,
    ) -> None: ...


class ContractEventNotifier:
    """契約イベントの通知を行うクラス

    通知失敗時には NotificationError を発生させるが、
    呼び出し元に影響与えないよう、ログ出力のみ行ってエラー自体は握り潰す
    """

    def __init__(  # noqa: PLR0913
        self,
        sns_client: SnsClient,
        stage: str,
        region: str,
        user_pool_id: str,
        log_group_name: str = "",
        log_stream_name: str = "",
    ) -> None:
        self.sns_client = sns_client
        self.stage = stage
        self.region = region
        self.user_pool_id = user_pool_id
        self.log_group_name = log_group_name
        self.log_stream_name = log_stream_name

    def notify_tenant_created(self, tenant: Tenant, contract_event: ContractEvent) -> None:
        message = {
            "version": "1.0",
            "source": "custom",
            "content": {
                "title": f"✅ テナントが追加されました | {self.stage}",
                "description": (
                    f"会社名: {tenant.legal_name}\n"
                    f"{self._format_customer_id_label(contract_event.distribution)}: {contract_event.customer_id}"
                ),
                "nextSteps": [
                    f"<{self._build_cognito_client_link(tenant.m2m_application_client_id)}|Cognitoアプリケーションクライアントを確認する>",
                ],
            },
        }
        try:
            self._publish(message)
        except NotificationError:
            logger.warning("Failed to send tenant creation notification; continuing")

    def notify_tenant_creation_failed(
        self,
        contract_event: ContractEvent,
        error_message: str,
    ) -> None:
        message = {
            "version": "1.0",
            "source": "custom",
            "content": {
                "title": f"❌ テナント追加に失敗しました | {self.stage}",
                "description": (
                    f"会社名: {contract_event.legal_name}\n"
                    f"{self._format_customer_id_label(contract_event.distribution)}: {contract_event.customer_id}\n"
                    f"\n"
                    f"エラー: {error_message}"
                ),
                "nextSteps": [
                    f"<{self._build_log_link()}|CloudWatch Logs を確認する>",
                ],
            },
        }
        try:
            self._publish(message)
        except NotificationError:
            logger.warning("Failed to send tenant creation failure notification; continuing")

    def notify_quota_allocation_added(self, tenant: Tenant, allocation: QuotaAllocation, contract_id: str) -> None:
        message = {
            "version": "1.0",
            "source": "custom",
            "content": {
                "title": f"✅ クォータ割り当てが追加されました | {self.stage}",
                "description": (
                    f"会社名: {tenant.legal_name}\n"
                    f"\n"
                    f"見積書番号: {contract_id}\n"
                    f"契約タイプ: {allocation.type.label}\n"
                    f"総量: {int(allocation.amount)}\n"
                    f"\n"
                    f"ステータス: {allocation.status.value}\n"
                    f"開始日時: {allocation.valid_from_date}\n"
                    f"終了日時: {allocation.valid_to_date}\n"
                ),
                "nextSteps": [],
            },
        }
        try:
            self._publish(message)
        except NotificationError:
            logger.warning("Failed to send quota allocation notification; continuing")

    def notify_quota_allocation_failed(self, tenant: Tenant, contract_event: ContractEvent, error_message: str) -> None:
        message = {
            "version": "1.0",
            "source": "custom",
            "content": {
                "title": f"❌ クォータ割り当ての追加に失敗しました | {self.stage}",
                "description": (
                    f"会社名: {tenant.legal_name}\n"
                    f"\n"
                    f"見積書番号: {contract_event.contract_id}\n"
                    f"契約タイプ: {contract_event.type.label}\n"
                    f"総量: {int(contract_event.amount)}\n"
                    f"\n"
                    f"開始日時: {contract_event.valid_from_date}\n"
                    f"終了日時: {contract_event.valid_to_date}\n"
                    f"\n"
                    f"エラー: {error_message}"
                ),
                "nextSteps": [
                    f"<{self._build_log_link()}|CloudWatch Logs を確認する>",
                ],
            },
        }
        try:
            self._publish(message)
        except NotificationError:
            logger.warning("Failed to send quota allocation failure notification; continuing")

    def _publish(self, message: dict) -> None:
        """SNS通知を実行し、失敗時は NotificationError を発生させる"""
        try:
            self.sns_client.publish(message)
        except Exception as e:
            raise NotificationError(f"Failed to send SNS notification: {e!s}") from e

    def _format_customer_id_label(self, distribution: Distribution) -> str:
        if distribution == Distribution.WHI_SFDC:
            return "WHI取引先ID"
        return "CYDAS取引先ID"

    def _build_cognito_client_link(self, application_client_id: str) -> str:
        return (
            f"https://{self.region}.console.aws.amazon.com/cognito/v2/idp/"
            f"user-pools/{self.user_pool_id}/applications/app-clients/"
            f"{application_client_id}/quick-setup-guide?region={self.region}"
        )

    def _build_log_link(self) -> str:
        """現在の Lambda 実行の CloudWatch Logs リンクを生成する"""
        log_group = urllib.parse.quote(
            self.log_group_name,
            safe="",
        ).replace("%", "$25")
        log_stream = urllib.parse.quote(
            self.log_stream_name,
            safe="",
        ).replace("%", "$25")
        return (
            f"https://console.aws.amazon.com/cloudwatch/home?region={self.region}"
            f"#logsV2:log-groups/log-group/{log_group}/log-events/{log_stream}"
        )


class NullContractEventNotifier:
    def notify_tenant_created(self, tenant: Tenant, contract_event: ContractEvent) -> None:
        pass

    def notify_tenant_creation_failed(self, contract_event: ContractEvent, error_message: str) -> None:
        pass

    def notify_quota_allocation_added(self, tenant: Tenant, allocation: QuotaAllocation, contract_id: str) -> None:
        pass

    def notify_quota_allocation_failed(self, tenant: Tenant, contract_event: ContractEvent, error_message: str) -> None:
        pass
