"""
Webhook infrastructure models for inbound and outbound webhook management.

Rewritten to use SQLModel to align with the app's ORM base (SQLModel),
ensuring relationships and queries work with existing AsyncSession usage.
"""

from __future__ import annotations

from typing import Optional, List, Dict, Any
from datetime import datetime
from enum import Enum
import uuid

from sqlalchemy import (
    Column,
    String,
    Integer,
    Boolean,
    DateTime,
    JSON,
    Text,
    ForeignKey,
    Index,
    Float,
    UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
from sqlmodel import Field
from fmatch.saas.db import Base

# Portable JSONB type that falls back to JSON for SQLite tests
# Postgres uses JSONB (indexed), SQLite uses JSON (text-based)
PortableJSONB = JSONB().with_variant(JSON, "sqlite")


class WebhookEventType(str, Enum):
    """Supported webhook event types."""

    # Job events
    JOB_STARTED = "job.started"
    JOB_COMPLETED = "job.completed"
    JOB_FAILED = "job.failed"

    # Match events
    MATCH_FOUND = "match.found"
    MATCH_APPROVED = "match.approved"
    MATCH_REJECTED = "match.rejected"
    MATCH_REVIEW_REQUIRED = "match.review_required"

    # Integration events
    SYNC_STARTED = "sync.started"
    SYNC_COMPLETED = "sync.completed"
    SYNC_FAILED = "sync.failed"

    # Data events
    RECORD_CREATED = "record.created"
    RECORD_UPDATED = "record.updated"
    RECORD_DELETED = "record.deleted"

    # Custom events
    CUSTOM = "custom"


class WebhookStatus(str, Enum):
    """Webhook delivery status."""

    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    SUCCESS = "success"
    FAILED = "failed"
    RETRYING = "retrying"
    DEAD_LETTER = "dead_letter"


class WebhookEndpoint(Base, table=True):
    """
    Webhook endpoint configuration for outbound webhooks.
    Each account can have multiple endpoints listening to different events.
    """

    __tablename__ = "webhook_endpoints"

    id: uuid.UUID = Field(
        default_factory=uuid.uuid4,
        sa_column=Column(PG_UUID(as_uuid=True), primary_key=True),
    )
    account_id: str = Field(
        sa_column=Column(String(36), ForeignKey("accounts.id"), nullable=False)
    )

    # Endpoint configuration
    name: str = Field(sa_column=Column(String(255), nullable=False))
    url: str = Field(sa_column=Column(Text, nullable=False))
    description: Optional[str] = Field(default=None, sa_column=Column(Text))

    # Events this endpoint subscribes to (JSONB on Postgres, JSON on SQLite)
    events: List[str] = Field(
        default_factory=list, sa_column=Column(PortableJSONB, nullable=False)
    )

    # Security
    secret: Optional[str] = Field(default=None, sa_column=Column(String(255)))
    headers: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))

    # Retry configuration
    retry_enabled: bool = Field(
        default=True, sa_column=Column(Boolean, nullable=False, default=True)
    )
    max_retries: int = Field(
        default=5, sa_column=Column(Integer, nullable=False, default=5)
    )
    retry_delay_seconds: int = Field(
        default=1, sa_column=Column(Integer, nullable=False, default=1)
    )
    retry_backoff_multiplier: float = Field(
        default=2.0, sa_column=Column(Float, nullable=False, default=2.0)
    )
    timeout_seconds: int = Field(
        default=30, sa_column=Column(Integer, nullable=False, default=30)
    )

    # Circuit breaker
    consecutive_failures: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    circuit_breaker_threshold: int = Field(
        default=10, sa_column=Column(Integer, nullable=False, default=10)
    )
    circuit_breaker_reset_after: Optional[datetime] = Field(
        default=None, sa_column=Column(DateTime)
    )

    # Batching configuration
    batch_enabled: bool = Field(
        default=False, sa_column=Column(Boolean, nullable=False, default=False)
    )
    batch_size: int = Field(
        default=100, sa_column=Column(Integer, nullable=False, default=100)
    )
    batch_window_seconds: int = Field(
        default=60, sa_column=Column(Integer, nullable=False, default=60)
    )

    # Status
    active: bool = Field(
        default=True, sa_column=Column(Boolean, nullable=False, default=True)
    )
    verified: bool = Field(
        default=False, sa_column=Column(Boolean, nullable=False, default=False)
    )
    last_success_at: Optional[datetime] = Field(
        default=None, sa_column=Column(DateTime)
    )
    last_failure_at: Optional[datetime] = Field(
        default=None, sa_column=Column(DateTime)
    )
    last_error: Optional[str] = Field(default=None, sa_column=Column(Text))

    # Stats
    total_deliveries: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    successful_deliveries: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    failed_deliveries: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )

    # Metadata
    created_at: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )
    updated_at: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )
    created_by: Optional[str] = Field(default=None, sa_column=Column(String(255)))

    # Relationships intentionally omitted to avoid SQLAlchemy 2.0 Mapped[] requirements.

    # Indexes for efficient querying
    __table_args__ = (
        Index("idx_webhook_endpoints_account_active", "account_id", "active"),
        Index("idx_webhook_endpoints_events", "events", postgresql_using="gin"),
        Index(
            "idx_webhook_endpoints_circuit_breaker", "active", "consecutive_failures"
        ),
        UniqueConstraint("account_id", "name", name="uq_webhook_endpoint_name"),
    )


class WebhookDelivery(Base, table=True):
    """
    Individual webhook delivery attempts.
    Tracks the lifecycle of each webhook delivery including retries.
    """

    __tablename__ = "webhook_deliveries"

    id: uuid.UUID = Field(
        default_factory=uuid.uuid4,
        sa_column=Column(PG_UUID(as_uuid=True), primary_key=True),
    )
    endpoint_id: uuid.UUID = Field(
        sa_column=Column(
            PG_UUID(as_uuid=True), ForeignKey("webhook_endpoints.id"), nullable=False
        )
    )

    # Event information
    event_type: str = Field(sa_column=Column(String(100), nullable=False))
    event_id: Optional[str] = Field(default=None, sa_column=Column(String(255)))

    # Payload
    payload: Dict[str, Any] = Field(sa_column=Column(JSON, nullable=False))
    headers: Optional[Dict[str, Any]] = Field(default=None, sa_column=Column(JSON))

    # Delivery status
    status: str = Field(
        default=WebhookStatus.PENDING.value,
        sa_column=Column(
            String(50), nullable=False, default=WebhookStatus.PENDING.value
        ),
    )
    attempts: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    max_attempts: int = Field(
        default=5, sa_column=Column(Integer, nullable=False, default=5)
    )

    # Response information
    response_status_code: Optional[int] = Field(default=None, sa_column=Column(Integer))
    response_body: Optional[str] = Field(default=None, sa_column=Column(Text))
    response_headers: Optional[Dict[str, Any]] = Field(
        default=None, sa_column=Column(JSON)
    )

    # Error tracking
    last_error: Optional[str] = Field(default=None, sa_column=Column(Text))
    error_count: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )

    # Timing
    created_at: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )
    scheduled_for: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )
    next_retry_at: Optional[datetime] = Field(default=None, sa_column=Column(DateTime))
    first_attempted_at: Optional[datetime] = Field(
        default=None, sa_column=Column(DateTime)
    )
    last_attempted_at: Optional[datetime] = Field(
        default=None, sa_column=Column(DateTime)
    )
    completed_at: Optional[datetime] = Field(default=None, sa_column=Column(DateTime))

    # Performance metrics
    latency_ms: Optional[int] = Field(default=None, sa_column=Column(Integer))
    processing_time_ms: Optional[int] = Field(default=None, sa_column=Column(Integer))

    # Batch support
    batch_id: Optional[uuid.UUID] = Field(
        default=None, sa_column=Column(PG_UUID(as_uuid=True))
    )
    batch_position: Optional[int] = Field(default=None, sa_column=Column(Integer))

    # Relationships intentionally omitted; use explicit joins in queries.

    # Indexes for queue processing
    __table_args__ = (
        Index("idx_webhook_deliveries_queue", "status", "scheduled_for"),
        Index("idx_webhook_deliveries_endpoint_status", "endpoint_id", "status"),
        Index("idx_webhook_deliveries_batch", "batch_id", "batch_position"),
        Index("idx_webhook_deliveries_retry", "status", "next_retry_at"),
        Index("idx_webhook_deliveries_created", "created_at"),
    )


class InboundWebhook(Base, table=True):
    """
    Tracks all inbound webhook requests for auditing and processing.
    """

    __tablename__ = "inbound_webhooks"

    id: uuid.UUID = Field(
        default_factory=uuid.uuid4,
        sa_column=Column(PG_UUID(as_uuid=True), primary_key=True),
    )
    account_id: Optional[str] = Field(
        default=None, sa_column=Column(String(36), ForeignKey("accounts.id"))
    )

    # Source information
    source: str = Field(sa_column=Column(String(100), nullable=False))
    source_ip: Optional[str] = Field(default=None, sa_column=Column(String(45)))
    user_agent: Optional[str] = Field(default=None, sa_column=Column(Text))

    # Request details
    method: str = Field(sa_column=Column(String(10), nullable=False))
    path: str = Field(sa_column=Column(Text, nullable=False))
    headers: Optional[Dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
    query_params: Optional[Dict[str, Any]] = Field(default=None, sa_column=Column(JSON))

    # Payload
    payload: Optional[Dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
    raw_body: Optional[str] = Field(default=None, sa_column=Column(Text))
    content_type: Optional[str] = Field(default=None, sa_column=Column(String(100)))
    payload_size: Optional[int] = Field(default=None, sa_column=Column(Integer))

    # Security
    signature: Optional[str] = Field(default=None, sa_column=Column(Text))
    signature_valid: Optional[bool] = Field(default=None, sa_column=Column(Boolean))
    api_key_used: Optional[str] = Field(default=None, sa_column=Column(String(255)))

    # Processing
    status: str = Field(
        default="received",
        sa_column=Column(String(50), nullable=False, default="received"),
    )
    processed_at: Optional[datetime] = Field(default=None, sa_column=Column(DateTime))
    processing_time_ms: Optional[int] = Field(default=None, sa_column=Column(Integer))

    # Error handling
    error: Optional[str] = Field(default=None, sa_column=Column(Text))
    error_code: Optional[str] = Field(default=None, sa_column=Column(String(50)))
    retry_count: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    should_retry: bool = Field(
        default=False, sa_column=Column(Boolean, nullable=False, default=False)
    )

    # Response
    response_status_code: Optional[int] = Field(default=None, sa_column=Column(Integer))
    response_body: Optional[Dict[str, Any]] = Field(
        default=None, sa_column=Column(JSON)
    )

    # Metadata
    received_at: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )
    integration_id: Optional[str] = Field(default=None, sa_column=Column(String(255)))
    correlation_id: Optional[str] = Field(default=None, sa_column=Column(String(255)))

    # Parsed data (after normalization)
    parsed_data: Optional[Dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
    data_format: Optional[str] = Field(default=None, sa_column=Column(String(50)))
    record_count: Optional[int] = Field(default=None, sa_column=Column(Integer))

    # Indexes for efficient querying
    __table_args__ = (
        Index("idx_inbound_webhooks_account_source", "account_id", "source"),
        Index("idx_inbound_webhooks_status_received", "status", "received_at"),
        Index("idx_inbound_webhooks_correlation", "correlation_id"),
        Index("idx_inbound_webhooks_integration", "integration_id", "received_at"),
        Index("idx_inbound_webhooks_processing", "status", "should_retry"),
    )


class WebhookApiKey(Base, table=True):
    """
    API keys for webhook authentication.
    """

    __tablename__ = "webhook_api_keys"

    id: uuid.UUID = Field(
        default_factory=uuid.uuid4,
        sa_column=Column(PG_UUID(as_uuid=True), primary_key=True),
    )
    account_id: str = Field(
        sa_column=Column(String(36), ForeignKey("accounts.id"), nullable=False)
    )

    # Key details
    name: str = Field(sa_column=Column(String(255), nullable=False))
    key_hash: str = Field(sa_column=Column(String(255), nullable=False, unique=True))
    key_prefix: str = Field(sa_column=Column(String(10), nullable=False))
    description: Optional[str] = Field(default=None, sa_column=Column(Text))

    # Permissions (lists stored as JSON for cross-dialect compatibility)
    scopes: List[str] = Field(default_factory=list, sa_column=Column(JSON))
    allowed_sources: Optional[List[str]] = Field(default=None, sa_column=Column(JSON))
    ip_whitelist: Optional[List[str]] = Field(default=None, sa_column=Column(JSON))

    # Rate limiting
    rate_limit_per_minute: int = Field(
        default=100, sa_column=Column(Integer, nullable=False, default=100)
    )
    rate_limit_per_hour: int = Field(
        default=1000, sa_column=Column(Integer, nullable=False, default=1000)
    )
    rate_limit_per_day: int = Field(
        default=10000, sa_column=Column(Integer, nullable=False, default=10000)
    )

    # Status
    active: bool = Field(
        default=True, sa_column=Column(Boolean, nullable=False, default=True)
    )
    expires_at: Optional[datetime] = Field(default=None, sa_column=Column(DateTime))
    last_used_at: Optional[datetime] = Field(default=None, sa_column=Column(DateTime))
    last_used_ip: Optional[str] = Field(default=None, sa_column=Column(String(45)))

    # Usage stats
    total_requests: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    successful_requests: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )
    failed_requests: int = Field(
        default=0, sa_column=Column(Integer, nullable=False, default=0)
    )

    # Metadata
    created_at: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )
    created_by: Optional[str] = Field(default=None, sa_column=Column(String(255)))
    revoked_at: Optional[datetime] = Field(default=None, sa_column=Column(DateTime))
    revoked_by: Optional[str] = Field(default=None, sa_column=Column(String(255)))
    revoke_reason: Optional[str] = Field(default=None, sa_column=Column(Text))

    # Indexes
    __table_args__ = (
        Index("idx_webhook_api_keys_account_active", "account_id", "active"),
        Index("idx_webhook_api_keys_prefix", "key_prefix"),
        Index("idx_webhook_api_keys_expires", "active", "expires_at"),
        UniqueConstraint("account_id", "name", name="uq_webhook_api_key_name"),
    )


class WebhookEventLog(Base, table=True):
    """
    Event log for all webhook-related activities for auditing.
    """

    __tablename__ = "webhook_event_logs"

    id: uuid.UUID = Field(
        default_factory=uuid.uuid4,
        sa_column=Column(PG_UUID(as_uuid=True), primary_key=True),
    )
    account_id: Optional[str] = Field(
        default=None, sa_column=Column(String(36), ForeignKey("accounts.id"))
    )

    # Event details
    event_type: str = Field(sa_column=Column(String(100), nullable=False))
    event_category: Optional[str] = Field(default=None, sa_column=Column(String(50)))

    # Related entities
    endpoint_id: Optional[uuid.UUID] = Field(
        default=None,
        sa_column=Column(PG_UUID(as_uuid=True), ForeignKey("webhook_endpoints.id")),
    )
    delivery_id: Optional[uuid.UUID] = Field(
        default=None,
        sa_column=Column(PG_UUID(as_uuid=True), ForeignKey("webhook_deliveries.id")),
    )
    inbound_id: Optional[uuid.UUID] = Field(
        default=None,
        sa_column=Column(PG_UUID(as_uuid=True), ForeignKey("inbound_webhooks.id")),
    )
    api_key_id: Optional[uuid.UUID] = Field(
        default=None,
        sa_column=Column(PG_UUID(as_uuid=True), ForeignKey("webhook_api_keys.id")),
    )

    # Event data
    description: Optional[str] = Field(default=None, sa_column=Column(Text))
    # Use a different attribute name to avoid SQLAlchemy's reserved 'metadata'
    event_metadata: Optional[Dict[str, Any]] = Field(
        default=None, sa_column=Column(JSON, name="metadata")
    )

    # Actor
    actor_type: Optional[str] = Field(default=None, sa_column=Column(String(50)))
    actor_id: Optional[str] = Field(default=None, sa_column=Column(String(255)))
    actor_ip: Optional[str] = Field(default=None, sa_column=Column(String(45)))

    # Timestamp
    occurred_at: datetime = Field(
        default_factory=datetime.utcnow, sa_column=Column(DateTime, nullable=False)
    )

    # Indexes
    __table_args__ = (
        Index("idx_webhook_event_logs_account_time", "account_id", "occurred_at"),
        Index("idx_webhook_event_logs_category_type", "event_category", "event_type"),
        Index("idx_webhook_event_logs_endpoint", "endpoint_id", "occurred_at"),
    )
