"""
Webhook Service - Handles webhook delivery and management
"""
import hashlib
import hmac
import json
import secrets
import logging
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any

import httpx
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload

from app.models.webhook import (
    ClientWebhook,
    WebhookDeliveryLog,
    WebhookEventType,
    WebhookDeliveryStatus
)

logger = logging.getLogger(__name__)


class WebhookService:
    """Service for managing and delivering webhooks"""

    def __init__(self):
        self.default_timeout = 30
        self.default_max_retries = 3
        self.default_retry_delay = 60

    def generate_signature(self, payload: str, secret: str) -> str:
        """
        Generate HMAC-SHA256 signature for webhook payload
        """
        return hmac.new(
            secret.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()

    def generate_event_id(self) -> str:
        """Generate unique event ID"""
        return f"evt_{secrets.token_hex(16)}"

    async def create_webhook(
        self,
        db: AsyncSession,
        client_id: int,
        name: str,
        url: str,
        events: List[str],
        description: Optional[str] = None,
        custom_headers: Optional[Dict[str, str]] = None,
        max_retries: int = 3,
        timeout_seconds: int = 30
    ) -> ClientWebhook:
        """Create a new webhook configuration"""
        webhook = ClientWebhook(
            client_id=client_id,
            name=name,
            description=description,
            url=url,
            secret_key=ClientWebhook.generate_secret(),
            events=events,
            custom_headers=custom_headers,
            max_retries=max_retries,
            timeout_seconds=timeout_seconds
        )
        db.add(webhook)
        await db.commit()
        await db.refresh(webhook)
        return webhook

    async def get_webhook(
        self,
        db: AsyncSession,
        webhook_id: int,
        client_id: Optional[int] = None
    ) -> Optional[ClientWebhook]:
        """Get webhook by ID"""
        query = select(ClientWebhook).where(
            ClientWebhook.id == webhook_id,
            ClientWebhook.deleted_at.is_(None)
        )
        if client_id:
            query = query.where(ClientWebhook.client_id == client_id)

        result = await db.execute(query)
        return result.scalar_one_or_none()

    async def get_client_webhooks(
        self,
        db: AsyncSession,
        client_id: int,
        is_active: Optional[bool] = None
    ) -> List[ClientWebhook]:
        """Get all webhooks for a client"""
        query = select(ClientWebhook).where(
            ClientWebhook.client_id == client_id,
            ClientWebhook.deleted_at.is_(None)
        )
        if is_active is not None:
            query = query.where(ClientWebhook.is_active == is_active)

        result = await db.execute(query)
        return list(result.scalars().all())

    async def update_webhook(
        self,
        db: AsyncSession,
        webhook_id: int,
        client_id: int,
        **updates
    ) -> Optional[ClientWebhook]:
        """Update webhook configuration"""
        webhook = await self.get_webhook(db, webhook_id, client_id)
        if not webhook:
            return None

        for key, value in updates.items():
            if hasattr(webhook, key) and value is not None:
                setattr(webhook, key, value)

        await db.commit()
        await db.refresh(webhook)
        return webhook

    async def delete_webhook(
        self,
        db: AsyncSession,
        webhook_id: int,
        client_id: int
    ) -> bool:
        """Soft delete a webhook"""
        webhook = await self.get_webhook(db, webhook_id, client_id)
        if not webhook:
            return False

        webhook.deleted_at = datetime.utcnow()
        await db.commit()
        return True

    async def regenerate_secret(
        self,
        db: AsyncSession,
        webhook_id: int,
        client_id: int
    ) -> Optional[str]:
        """Regenerate webhook secret"""
        webhook = await self.get_webhook(db, webhook_id, client_id)
        if not webhook:
            return None

        new_secret = ClientWebhook.generate_secret()
        webhook.secret_key = new_secret
        await db.commit()
        return new_secret

    async def get_webhooks_for_event(
        self,
        db: AsyncSession,
        client_id: int,
        event_type: str
    ) -> List[ClientWebhook]:
        """Get all active webhooks subscribed to an event type"""
        query = select(ClientWebhook).where(
            ClientWebhook.client_id == client_id,
            ClientWebhook.is_active == True,
            ClientWebhook.deleted_at.is_(None)
        )
        result = await db.execute(query)
        webhooks = result.scalars().all()

        # Filter by event type
        return [w for w in webhooks if event_type in w.events]

    async def trigger_event(
        self,
        db: AsyncSession,
        client_id: int,
        event_type: str,
        data: Dict[str, Any]
    ) -> List[WebhookDeliveryLog]:
        """
        Trigger a webhook event for a client
        Returns list of delivery logs created
        """
        webhooks = await self.get_webhooks_for_event(db, client_id, event_type)
        delivery_logs = []

        for webhook in webhooks:
            event_id = self.generate_event_id()
            payload = {
                "id": event_id,
                "type": event_type,
                "created_at": datetime.utcnow().isoformat(),
                "data": data
            }

            # Create delivery log
            log = WebhookDeliveryLog(
                webhook_id=webhook.id,
                event_type=event_type,
                event_id=event_id,
                payload=payload,
                status=WebhookDeliveryStatus.PENDING,
                max_attempts=webhook.max_retries
            )
            db.add(log)
            await db.commit()
            await db.refresh(log)

            # Attempt delivery
            await self.deliver_webhook(db, log, webhook)
            delivery_logs.append(log)

        return delivery_logs

    async def deliver_webhook(
        self,
        db: AsyncSession,
        log: WebhookDeliveryLog,
        webhook: ClientWebhook
    ) -> bool:
        """
        Deliver a webhook payload
        Returns True if successful
        """
        log.attempt_count += 1
        log.status = WebhookDeliveryStatus.RETRYING if log.attempt_count > 1 else WebhookDeliveryStatus.PENDING

        payload_str = json.dumps(log.payload)
        signature = self.generate_signature(payload_str, webhook.secret_key)

        headers = {
            "Content-Type": "application/json",
            "X-Webhook-Signature": f"sha256={signature}",
            "X-Webhook-Event": log.event_type,
            "X-Webhook-Event-Id": log.event_id,
            "X-Webhook-Timestamp": datetime.utcnow().isoformat(),
            "User-Agent": "AI360-Webhook/1.0"
        }

        # Add custom headers
        if webhook.custom_headers:
            headers.update(webhook.custom_headers)

        start_time = datetime.utcnow()

        try:
            async with httpx.AsyncClient(timeout=webhook.timeout_seconds) as client:
                response = await client.post(
                    webhook.url,
                    content=payload_str,
                    headers=headers
                )

            end_time = datetime.utcnow()
            response_time = int((end_time - start_time).total_seconds() * 1000)

            log.response_status_code = response.status_code
            log.response_body = response.text[:1000] if response.text else None
            log.response_time_ms = response_time

            # Check if successful (2xx status)
            if 200 <= response.status_code < 300:
                log.status = WebhookDeliveryStatus.DELIVERED
                log.delivered_at = datetime.utcnow()

                # Update webhook stats
                webhook.total_deliveries += 1
                webhook.successful_deliveries += 1
                webhook.last_delivery_at = datetime.utcnow()
                webhook.last_success_at = datetime.utcnow()

                await db.commit()
                logger.info(f"Webhook delivered successfully: {webhook.url} - Event: {log.event_type}")
                return True
            else:
                raise Exception(f"HTTP {response.status_code}: {response.text[:200]}")

        except Exception as e:
            error_message = str(e)
            log.error_message = error_message
            log.response_time_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)

            if log.attempt_count >= log.max_attempts:
                log.status = WebhookDeliveryStatus.FAILED
                webhook.failed_deliveries += 1
                webhook.last_failure_at = datetime.utcnow()
                logger.error(f"Webhook delivery failed permanently: {webhook.url} - {error_message}")
            else:
                log.status = WebhookDeliveryStatus.RETRYING
                log.next_retry_at = datetime.utcnow() + timedelta(
                    seconds=webhook.retry_delay_seconds * log.attempt_count
                )
                logger.warning(f"Webhook delivery failed, will retry: {webhook.url} - {error_message}")

            webhook.total_deliveries += 1
            webhook.last_delivery_at = datetime.utcnow()

            await db.commit()
            return False

    async def retry_failed_webhooks(self, db: AsyncSession) -> int:
        """
        Retry all pending webhook deliveries
        Returns number of retried webhooks
        """
        query = select(WebhookDeliveryLog).where(
            WebhookDeliveryLog.status == WebhookDeliveryStatus.RETRYING,
            WebhookDeliveryLog.next_retry_at <= datetime.utcnow()
        ).options(selectinload(WebhookDeliveryLog.webhook))

        result = await db.execute(query)
        logs = result.scalars().all()

        retried = 0
        for log in logs:
            if log.webhook and log.webhook.is_active:
                await self.deliver_webhook(db, log, log.webhook)
                retried += 1

        return retried

    async def get_delivery_logs(
        self,
        db: AsyncSession,
        webhook_id: int,
        client_id: int,
        limit: int = 50,
        offset: int = 0
    ) -> List[WebhookDeliveryLog]:
        """Get delivery logs for a webhook"""
        # Verify webhook belongs to client
        webhook = await self.get_webhook(db, webhook_id, client_id)
        if not webhook:
            return []

        query = select(WebhookDeliveryLog).where(
            WebhookDeliveryLog.webhook_id == webhook_id
        ).order_by(
            WebhookDeliveryLog.created_at.desc()
        ).limit(limit).offset(offset)

        result = await db.execute(query)
        return list(result.scalars().all())

    async def test_webhook(
        self,
        db: AsyncSession,
        webhook_id: int,
        client_id: int
    ) -> Dict[str, Any]:
        """Send a test webhook delivery"""
        webhook = await self.get_webhook(db, webhook_id, client_id)
        if not webhook:
            return {"success": False, "error": "Webhook not found"}

        test_data = {
            "test": True,
            "message": "This is a test webhook delivery from AI360 Solutions",
            "timestamp": datetime.utcnow().isoformat()
        }

        event_id = self.generate_event_id()
        payload = {
            "id": event_id,
            "type": "test.webhook",
            "created_at": datetime.utcnow().isoformat(),
            "data": test_data
        }

        payload_str = json.dumps(payload)
        signature = self.generate_signature(payload_str, webhook.secret_key)

        headers = {
            "Content-Type": "application/json",
            "X-Webhook-Signature": f"sha256={signature}",
            "X-Webhook-Event": "test.webhook",
            "X-Webhook-Event-Id": event_id,
            "X-Webhook-Timestamp": datetime.utcnow().isoformat(),
            "User-Agent": "AI360-Webhook/1.0"
        }

        if webhook.custom_headers:
            headers.update(webhook.custom_headers)

        try:
            async with httpx.AsyncClient(timeout=webhook.timeout_seconds) as client:
                response = await client.post(
                    webhook.url,
                    content=payload_str,
                    headers=headers
                )

            return {
                "success": 200 <= response.status_code < 300,
                "status_code": response.status_code,
                "response_body": response.text[:500] if response.text else None,
                "event_id": event_id
            }

        except Exception as e:
            return {
                "success": False,
                "error": str(e),
                "event_id": event_id
            }


# Singleton instance
webhook_service = WebhookService()
