"""Webhook utilities for GrowFlow Billing.

This module provides utilities for handling webhooks from GrowFlow Billing:
- Signature verification using HMAC-SHA256
- Event parsing and validation
- Typed webhook event dataclasses
"""

import hashlib
import hmac
import json
from dataclasses import dataclass, field
from typing import Any, Literal

# Supported webhook event types
WebhookEventType = Literal[
    "subscription.created",
    "subscription.updated",
    "subscription.canceled",
    "subscription.trial_ending",
    "invoice.paid",
    "invoice.payment_failed",
    "subscriber.created",
    "subscriber.updated",
]

# All valid event types for runtime validation
VALID_EVENT_TYPES: tuple[str, ...] = (
    "subscription.created",
    "subscription.updated",
    "subscription.canceled",
    "subscription.trial_ending",
    "invoice.paid",
    "invoice.payment_failed",
    "subscriber.created",
    "subscriber.updated",
)


class WebhookSignatureError(Exception):
    """Error thrown when webhook signature verification fails."""

    def __init__(self, message: str = "Invalid webhook signature"):
        self.message = message
        super().__init__(message)


class WebhookParseError(Exception):
    """Error thrown when webhook payload parsing fails."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)


@dataclass
class SubscriptionData:
    """Subscription data in webhook payload."""

    id: str
    status: str
    billing_cycle: str
    stripe_subscription_id: str | None = None
    current_period_start: str | None = None
    current_period_end: str | None = None
    trial_end: str | None = None
    canceled_at: str | None = None


@dataclass
class SubscriberData:
    """Subscriber data in webhook payload."""

    id: str
    external_id: str
    email: str
    name: str | None = None


@dataclass
class PlanData:
    """Plan data in webhook payload."""

    id: str
    slug: str
    name: str


@dataclass
class InvoiceData:
    """Invoice data in webhook payload."""

    stripe_invoice_id: str | None = None
    number: str | None = None
    status: str | None = None
    currency: str | None = None
    amount_due: int | None = None
    amount_paid: int | None = None
    hosted_invoice_url: str | None = None
    invoice_pdf: str | None = None


@dataclass
class WebhookEvent:
    """Parsed webhook event from GrowFlow Billing."""

    event_type: str
    event_id: str
    timestamp: str
    data: dict[str, Any] = field(default_factory=dict)

    @property
    def subscription(self) -> SubscriptionData | None:
        """Get subscription data from event."""
        sub_data = self.data.get("subscription")
        if not sub_data:
            return None
        return SubscriptionData(
            id=sub_data.get("id", ""),
            status=sub_data.get("status", ""),
            billing_cycle=sub_data.get("billing_cycle", ""),
            stripe_subscription_id=sub_data.get("stripe_subscription_id"),
            current_period_start=sub_data.get("current_period_start"),
            current_period_end=sub_data.get("current_period_end"),
            trial_end=sub_data.get("trial_end"),
            canceled_at=sub_data.get("canceled_at"),
        )

    @property
    def subscriber(self) -> SubscriberData | None:
        """Get subscriber data from event."""
        sub_data = self.data.get("subscriber")
        if not sub_data:
            return None
        return SubscriberData(
            id=sub_data.get("id", ""),
            external_id=sub_data.get("external_id", ""),
            email=sub_data.get("email", ""),
            name=sub_data.get("name"),
        )

    @property
    def plan(self) -> PlanData | None:
        """Get plan data from event."""
        plan_data = self.data.get("plan")
        if not plan_data:
            return None
        return PlanData(
            id=plan_data.get("id", ""),
            slug=plan_data.get("slug", ""),
            name=plan_data.get("name", ""),
        )

    @property
    def invoice(self) -> InvoiceData | None:
        """Get invoice data from event."""
        inv_data = self.data.get("invoice")
        if not inv_data:
            return None
        return InvoiceData(
            stripe_invoice_id=inv_data.get("stripe_invoice_id"),
            number=inv_data.get("number"),
            status=inv_data.get("status"),
            currency=inv_data.get("currency"),
            amount_due=inv_data.get("amount_due"),
            amount_paid=inv_data.get("amount_paid"),
            hosted_invoice_url=inv_data.get("hosted_invoice_url"),
            invoice_pdf=inv_data.get("invoice_pdf"),
        )

    def is_subscription_event(self) -> bool:
        """Check if event is a subscription event."""
        return self.event_type.startswith("subscription.")

    def is_invoice_event(self) -> bool:
        """Check if event is an invoice event."""
        return self.event_type.startswith("invoice.")

    def is_subscriber_event(self) -> bool:
        """Check if event is a subscriber event."""
        return self.event_type.startswith("subscriber.")


def verify_webhook_signature(
    payload: bytes,
    signature: str,
    secret: str,
) -> bool:
    """Verify HMAC-SHA256 signature of webhook payload.

    GrowFlow Billing signs webhook payloads using HMAC-SHA256.
    The signature is sent in the X-GrowFlow-Signature header.

    Args:
        payload: Raw request body as bytes
        signature: Value from X-GrowFlow-Signature header (format: 'sha256=...')
        secret: Your webhook secret from GrowFlow Billing dashboard

    Returns:
        True if signature is valid, False otherwise

    Example:
        from fastapi import Request, HTTPException
        from growflow_billing import verify_webhook_signature

        @app.post("/webhooks/growflow")
        async def handle_webhook(request: Request):
            payload = await request.body()
            signature = request.headers.get("X-GrowFlow-Signature", "")

            if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
                raise HTTPException(401, "Invalid signature")

            # Process webhook...
    """
    expected = "sha256=" + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, signature)


def parse_webhook_event(
    payload: bytes | str | dict[str, Any],
) -> WebhookEvent:
    """Parse webhook payload into a WebhookEvent.

    Note: This does NOT verify the signature. Use construct_event()
    for combined verification and parsing.

    Args:
        payload: Raw request body (bytes/str) or parsed dict

    Returns:
        Parsed WebhookEvent

    Raises:
        WebhookParseError: If payload is invalid or missing required fields
    """
    if isinstance(payload, bytes):
        try:
            data = json.loads(payload.decode("utf-8"))
        except json.JSONDecodeError as e:
            raise WebhookParseError(f"Invalid JSON payload: {e}") from e
    elif isinstance(payload, str):
        try:
            data = json.loads(payload)
        except json.JSONDecodeError as e:
            raise WebhookParseError(f"Invalid JSON payload: {e}") from e
    else:
        data = payload

    event_type = data.get("event_type")
    if not event_type:
        raise WebhookParseError("Missing 'event_type' in payload")

    if event_type not in VALID_EVENT_TYPES:
        raise WebhookParseError(f"Unknown event type: {event_type}")

    event_id = data.get("event_id")
    if not event_id:
        raise WebhookParseError("Missing 'event_id' in payload")

    timestamp = data.get("timestamp")
    if not timestamp:
        raise WebhookParseError("Missing 'timestamp' in payload")

    event_data = data.get("data", {})

    return WebhookEvent(
        event_type=event_type,
        event_id=event_id,
        timestamp=timestamp,
        data=event_data,
    )


def construct_event(
    payload: bytes | str | dict[str, Any],
    signature: str,
    secret: str,
) -> WebhookEvent:
    """Verify signature and construct a WebhookEvent.

    This is the recommended way to handle incoming webhooks as it
    combines signature verification and parsing in one step.

    Args:
        payload: Raw request body or parsed dict
        signature: Value from X-GrowFlow-Signature header
        secret: Your webhook secret

    Returns:
        Parsed WebhookEvent

    Raises:
        WebhookSignatureError: If signature verification fails
        WebhookParseError: If payload parsing fails

    Example:
        from fastapi import Request, HTTPException
        from growflow_billing import construct_event, WebhookSignatureError

        @app.post("/webhooks/growflow")
        async def handle_webhook(request: Request):
            payload = await request.body()
            signature = request.headers.get("X-GrowFlow-Signature", "")

            try:
                event = construct_event(payload, signature, WEBHOOK_SECRET)
            except WebhookSignatureError:
                raise HTTPException(401, "Invalid signature")

            match event.event_type:
                case "subscription.created":
                    subscription = event.subscription
                    # Handle new subscription...
                case "invoice.paid":
                    invoice = event.invoice
                    # Handle paid invoice...

            return {"status": "ok"}
    """
    # Convert to bytes for signature verification
    if isinstance(payload, str):
        payload_bytes = payload.encode("utf-8")
    elif isinstance(payload, dict):
        payload_bytes = json.dumps(payload).encode("utf-8")
    else:
        payload_bytes = payload

    if not verify_webhook_signature(payload_bytes, signature, secret):
        raise WebhookSignatureError()

    return parse_webhook_event(payload)
