"""Webhook handlers for external integrations (GitHub, GitLab, Jira).

Listens to TicketStatusChanged events and syncs to external systems.
"""

from __future__ import annotations

import asyncio
from typing import Any, Dict

import httpx
import structlog

from proxym.domain.events import TicketStatusChanged, TaskCommentAdded, TaskUpdated
from proxym.domain.setup import get_event_store
from proxym.dashboard._lifecycle import LifecycleMixin

logger = structlog.get_logger()


class WebhookManager(LifecycleMixin):
    """Manages webhooks to external systems."""
    
    _logger_name = "webhook_manager"
    
    def __init__(self) -> None:
        super().__init__()
        self._event_store = get_event_store()
        self._webhooks: Dict[str, str] = {}  # system -> webhook_url
    
    def register_webhook(self, system: str, url: str) -> None:
        """Register a webhook URL for a system."""
        self._webhooks[system] = url
        logger.info("webhook_registered", system=system, url=url)
    
    async def _event_loop(self) -> None:
        """Main event processing loop."""
        while self._running:
            try:
                # Get events since last check
                events = self._event_store.get_events(since="last_webhook_run")
                for event in events:
                    if isinstance(event, TicketStatusChanged):
                        await self._handle_status_change(event)
                    elif isinstance(event, TaskUpdated) and any(
                        key in event.data for key in ("status", "current_status", "previous_status")
                    ):
                        await self._handle_status_change(event)
                    elif isinstance(event, TaskCommentAdded):
                        await self._handle_status_change(event)
                # Mark events as processed
                self._event_store.mark_processed("webhook", events)
                await asyncio.sleep(5.0)  # Poll every 5 seconds
            except asyncio.CancelledError:
                raise
            except Exception as exc:
                logger.error("webhook_event_loop_error", error=str(exc))
                await asyncio.sleep(10.0)
    
    async def _handle_status_change(self, event: TicketStatusChanged | TaskUpdated | TaskCommentAdded) -> None:
        """Handle ticket status/comment changes and sync them to webhooks."""
        data = event.data
        event_type = event.event_type
        ticket_id = data.get("ticket_id") or event.aggregate_id

        # Normalise a common payload shape first.
        payload: Dict[str, Any] = {
            "event_type": event_type,
            "ticket_id": ticket_id,
            "ticket_event": data,
            "timestamp": data.get("timestamp", event.timestamp),
            "actor": event.actor,
        }

        # TaskUpdated is emitted for manual/API updates; include status changes.
        if isinstance(event, TaskUpdated):
            payload["old_status"] = data.get("previous_status") or data.get("old_status")
            payload["new_status"] = data.get("current_status") or data.get("status")
            payload["comment"] = data.get("comment", "")
            payload["trigger"] = {
                "event": "TaskUpdated",
                "job_id": data.get("job_id", ""),
                "phase": data.get("phase", "manual_update"),
            }

        # TaskCommentAdded is a plain audit/log/comment event.
        elif isinstance(event, TaskCommentAdded):
            payload["comment"] = {
                "id": data.get("comment_id", ""),
                "author": data.get("author", ""),
                "content": data.get("content", ""),
            }
            payload["trigger"] = {
                "event": "TaskCommentAdded",
                "job_id": data.get("job_id", ""),
            }

        # Backward-compatible TicketStatusChanged event emitted by the orchestrator.
        else:
            payload["old_status"] = data.get("old_status")
            payload["new_status"] = data.get("new_status")
            payload["comment"] = data.get("comment", "")
            payload["trigger"] = {
                "event": data.get("trigger_event"),
                "job_id": data.get("trigger_job_id"),
                "phase": data.get("phase", ""),
            }
        
        # Build webhook payload
        # Send to all registered webhooks
        for system, url in self._webhooks.items():
            try:
                await self._send_webhook(url, payload, system)
            except Exception as exc:
                logger.error("webhook_send_failed", system=system, error=str(exc))
    
    async def _send_webhook(self, url: str, payload: Dict[str, Any], system: str) -> None:
        """Send webhook to external system."""
        # System-specific payload transformations
        if system == "github":
            payload = self._transform_for_github(payload)
        elif system == "gitlab":
            payload = self._transform_for_gitlab(payload)
        elif system == "jira":
            payload = self._transform_for_jira(payload)
        
        async with httpx.AsyncClient(timeout=10.0) as client:
            response = await client.post(
                url,
                json=payload,
                headers={
                    "Content-Type": "application/json",
                    "User-Agent": "proxym-webhook/1.0",
                    "X-Proxym-Event": str(payload.get("event_type", "ticket.status_changed"))
                }
            )
            response.raise_for_status()
            logger.info("webhook_sent", system=system, url=url, status=response.status_code)
    
    def _transform_for_github(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Transform payload for GitHub."""
        status_map = {
            "todo": "open",
            "in_progress": "open",
            "in_review": "open",
            "done": "closed",
            "cancelled": "closed",
        }

        new_status = status_map.get(payload.get("new_status", ""), "open")
        comment = payload.get("comment")

        if isinstance(comment, dict) and comment.get("content"):
            return {
                "action": "commented",
                "issue": {
                    "number": self._extract_issue_number(payload["ticket_id"]),
                    "body": comment.get("content", ""),
                },
                "comment": comment,
                "proxym": payload,
            }

        return {
            "action": "updated" if new_status == "open" else "closed",
            "issue": {
                "number": self._extract_issue_number(payload["ticket_id"]),
                "state": new_status,
                "body": f"Status changed from {payload.get('old_status')} to {payload.get('new_status')}",
            },
            "proxym": payload,
        }
    
    def _transform_for_gitlab(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Transform payload for GitLab."""
        status_map = {
            "todo": "opened",
            "in_progress": "opened",
            "in_review": "opened",
            "done": "closed",
            "cancelled": "closed",
        }

        comment = payload.get("comment")
        if isinstance(comment, dict) and comment.get("content"):
            return {
                "object_kind": "note",
                "event_type": "note",
                "object_attributes": {
                    "note": comment.get("content", ""),
                    "author": comment.get("author", ""),
                    "noteable_type": "Issue",
                    "noteable_id": self._extract_issue_number(payload["ticket_id"]),
                },
                "proxym": payload,
            }
        
        return {
            "object_kind": "issue",
            "object_attributes": {
                "id": self._extract_issue_number(payload["ticket_id"]),
                "state": status_map.get(payload["new_status"], "opened"),
                "action": "update",
            },
            "proxym": payload,
        }
    
    def _transform_for_jira(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Transform payload for Jira."""
        status_map = {
            "todo": "To Do",
            "in_progress": "In Progress",
            "in_review": "In Review",
            "done": "Done",
            "cancelled": "Cancelled",
        }

        comment = payload.get("comment")
        if isinstance(comment, dict) and comment.get("content"):
            return {
                "issue": {
                    "key": payload["ticket_id"],
                    "fields": {
                        "comment": {
                            "body": comment.get("content", ""),
                            "author": comment.get("author", ""),
                        }
                    },
                },
                "proxym": payload,
            }
        
        return {
            "issue": {
                "key": payload["ticket_id"],
                "fields": {
                    "status": {
                        "name": status_map.get(payload.get("new_status", ""), "To Do"),
                    },
                    "summary": f"Status: {payload.get('old_status')} → {payload.get('new_status')}",
                    "description": f"Automated status update via Proxym orchestrator\n\nTrigger: {payload.get('trigger', {}).get('event', '')}",
                },
            },
            "proxym": payload,
        }
    
    def _extract_issue_number(self, ticket_id: str) -> int:
        """Extract numeric issue number from ticket ID like TKT-12345."""
        try:
            # Extract the hex part and convert to decimal
            hex_part = ticket_id.split("-")[1]
            return int(hex_part, 16)
        except (IndexError, ValueError):
            return 0


# Convenience functions for registering webhooks
def register_github_webhook(url: str) -> None:
    """Register GitHub webhook URL."""
    manager = WebhookManager.get()
    manager.register_webhook("github", url)


def register_gitlab_webhook(url: str) -> None:
    """Register GitLab webhook URL."""
    manager = WebhookManager.get()
    manager.register_webhook("gitlab", url)


def register_jira_webhook(url: str) -> None:
    """Register Jira webhook URL."""
    manager = WebhookManager.get()
    manager.register_webhook("jira", url)
