from django.db import models
from django.utils.text import slugify
from django.core.validators import URLValidator
from django.utils import timezone
import logging

logger = logging.getLogger(__name__)


class Organization(models.Model):
    """
    Organization/University model for storing external organization data.
    Supports multi-tenancy with unique external_id tracking.
    """

    class OrganizationStatus(models.TextChoices):
        ACTIVE = "active", "Active"
        INACTIVE = "inactive", "Inactive"
        SUSPENDED = "suspended", "Suspended"
        ARCHIVED = "archived", "Archived"

    # Primary fields
    organization_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255, unique=True)
    slug = models.SlugField(unique=True, max_length=255)
    code = models.CharField(max_length=50, unique=True)
    email = models.EmailField(unique=True)
    phone = models.CharField(max_length=20, blank=True)
    website = models.URLField(blank=True, null=True)
    description = models.TextField(blank=True)

    # Address fields
    address_line1 = models.CharField(max_length=255, blank=True)
    address_line2 = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=100, blank=True)
    state = models.CharField(max_length=100, blank=True)
    postal_code = models.CharField(max_length=20, blank=True)
    country = models.CharField(max_length=100, blank=True)

    # External API tracking
    external_id = models.CharField(
        max_length=255,
        unique=True,
        db_index=True,
        help_text="Unique identifier from external API source"
    )
    external_source = models.CharField(
        max_length=100,
        default="default_source",
        help_text="Name of external API source"
    )
    external_metadata = models.JSONField(
        default=dict,
        blank=True,
        help_text="Store additional metadata from external API"
    )

    # Status and timestamps
    status = models.CharField(
        max_length=20,
        choices=OrganizationStatus.choices,
        default=OrganizationStatus.ACTIVE
    )
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_synced = models.DateTimeField(
        null=True,
        blank=True,
        help_text="Timestamp of last successful sync"
    )

    class Meta:
        db_table = "organization"
        verbose_name = "Organization"
        verbose_name_plural = "Organizations"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["external_id", "external_source"]),
            models.Index(fields=["code"]),
            models.Index(fields=["status"]),
            models.Index(fields=["last_synced"]),
        ]

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.name} ({self.code})"

    def mark_synced(self):
        """Update last_synced timestamp."""
        self.last_synced = timezone.now()
        self.save(update_fields=["last_synced", "updated_at"])


class TenantManager(models.Manager):
    """
    Manager to filter querysets by organization.
    Supports global data if is_global=True or organization=None.
    """

    def for_organization(self, organization):
        """Filter data for a specific organization + global data."""
        queryset = self.get_queryset()
        
        # Check if model has is_global field
        has_is_global = any(f.name == 'is_global' for f in self.model._meta.fields)
        
        if organization is None:
            if has_is_global:
                return queryset.filter(is_global=True)
            return queryset.none()
        
        if has_is_global:
            return queryset.filter(
                models.Q(organization=organization) | models.Q(is_global=True)
            )
        return queryset.filter(organization=organization)


class BaseTenantModel(models.Model):
    """
    Abstract base model for all multi-tenant models.
    """
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name="%(class)s_data",
        null=True,  # Nullable for backward compatibility & global data
        blank=True,
        db_index=True
    )
    is_global = models.BooleanField(
        default=True,
        help_text="If true, this data is shared across all organizations."
    )

    objects = TenantManager()

    class Meta:
        abstract = True


class BaseOrganizationModel(models.Model):
    """
    Abstract base model for models that MUST belong to an organization and are NOT global.
    """
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name="%(class)s_private_data",
        null=False,
        blank=False,
        db_index=True,
        default=1
    )

    objects = TenantManager()

    class Meta:
        abstract = True


class SyncLog(models.Model):
    """
    Audit trail for all sync operations.
    Tracks what was synced, when, and any errors that occurred.
    """

    class SyncStatus(models.TextChoices):
        STARTED = "started", "Started"
        IN_PROGRESS = "in_progress", "In Progress"
        COMPLETED = "completed", "Completed"
        FAILED = "failed", "Failed"
        PARTIAL = "partial", "Partial Success"

    sync_id = models.AutoField(primary_key=True)
    external_source = models.CharField(max_length=100)
    status = models.CharField(
        max_length=20,
        choices=SyncStatus.choices,
        default=SyncStatus.STARTED
    )

    # Sync details
    started_at = models.DateTimeField(auto_now_add=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    duration_seconds = models.FloatField(null=True, blank=True)

    # Record counts
    total_fetched = models.IntegerField(default=0)
    created_count = models.IntegerField(default=0)
    updated_count = models.IntegerField(default=0)
    skipped_count = models.IntegerField(default=0)
    error_count = models.IntegerField(default=0)

    # Error information
    error_message = models.TextField(blank=True)
    error_traceback = models.TextField(blank=True)
    failed_records = models.JSONField(default=list, blank=True)

    # Additional metadata
    triggered_by = models.CharField(
        max_length=100,
        blank=True,
        help_text="User or system that triggered the sync"
    )
    sync_type = models.CharField(
        max_length=50,
        choices=[("full", "Full Sync"), ("partial", "Partial Sync")],
        default="full"
    )
    notes = models.TextField(blank=True)

    class Meta:
        db_table = "sync_log"
        verbose_name = "Sync Log"
        verbose_name_plural = "Sync Logs"
        ordering = ["-started_at"]
        indexes = [
            models.Index(fields=["external_source", "status"]),
            models.Index(fields=["started_at"]),
        ]

    def __str__(self):
        return f"Sync {self.sync_id} - {self.status} ({self.started_at})"

    @property
    def is_successful(self):
        return self.status == self.SyncStatus.COMPLETED

    @property
    def success_rate(self):
        if self.total_fetched == 0:
            return 0
        return ((self.created_count + self.updated_count) / self.total_fetched) * 100

    def mark_completed(self, duration=None):
        """Mark sync as completed."""
        self.completed_at = timezone.now()
        self.status = self.SyncStatus.COMPLETED
        if duration:
            self.duration_seconds = duration
        self.save()

    def mark_failed(self, error_message, error_traceback=None, duration=None):
        """Mark sync as failed."""
        self.completed_at = timezone.now()
        self.status = self.SyncStatus.FAILED
        self.error_message = error_message[:1000]  # Limit field size
        if error_traceback:
            self.error_traceback = error_traceback[:2000]  # Limit field size
        if duration:
            self.duration_seconds = duration
        self.save()

    def mark_partial(self, duration=None):
        """Mark sync as partially successful."""
        self.completed_at = timezone.now()
        self.status = self.SyncStatus.PARTIAL
        if duration:
            self.duration_seconds = duration
        self.save()


class SyncConfiguration(models.Model):
    """
    Configuration for external API sync operations.
    Stores credentials, endpoints, and settings.
    """

    config_id = models.AutoField(primary_key=True)
    source_name = models.CharField(
        max_length=100,
        unique=True,
        help_text="Unique identifier for this data source"
    )
    base_url = models.URLField(help_text="Base URL of external API")
    api_endpoint = models.CharField(
        max_length=255,
        default="/api/organizations/",
        help_text="REST endpoint relative to base_url"
    )
    auth_type = models.CharField(
        max_length=50,
        choices=[
            ("api_key", "API Key"),
            ("bearer_token", "Bearer Token"),
            ("basic_auth", "Basic Auth"),
        ],
        default="api_key"
    )
    auth_key = models.CharField(
        max_length=500,
        help_text="API key or token - stored encrypted in production"
    )

    # Request settings
    timeout_seconds = models.IntegerField(
        default=30,
        help_text="Request timeout in seconds"
    )
    max_retries = models.IntegerField(
        default=3,
        help_text="Maximum retry attempts for failed requests"
    )
    retry_delay_seconds = models.IntegerField(
        default=5,
        help_text="Initial delay for exponential backoff"
    )

    # Sync settings
    auto_sync_enabled = models.BooleanField(
        default=False,
        help_text="Enable automatic scheduled sync"
    )
    auto_sync_interval_hours = models.IntegerField(
        default=24,
        help_text="Hours between automatic syncs"
    )
    batch_size = models.IntegerField(
        default=100,
        help_text="Number of records per API request"
    )

    # Caching
    cache_enabled = models.BooleanField(
        default=True,
        help_text="Cache API responses"
    )
    cache_ttl_seconds = models.IntegerField(
        default=3600,
        help_text="Cache time-to-live in seconds"
    )

    # Features
    webhook_enabled = models.BooleanField(
        default=False,
        help_text="Enable webhook support for push updates"
    )
    webhook_secret = models.CharField(
        max_length=500,
        blank=True,
        help_text="Secret for webhook signature verification"
    )

    # Field mapping (JSON configuration)
    field_mapping = models.JSONField(
        default=dict,
        blank=True,
        help_text="Map external API fields to Organization model fields"
    )

    # Status and metadata
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_tested_at = models.DateTimeField(null=True, blank=True)
    last_test_successful = models.BooleanField(null=True, blank=True)

    class Meta:
        db_table = "sync_configuration"
        verbose_name = "Sync Configuration"
        verbose_name_plural = "Sync Configurations"

    def __str__(self):
        return f"Sync Config - {self.source_name}"

    @property
    def full_endpoint_url(self):
        """Get complete endpoint URL."""
        base = self.base_url.rstrip("/")
        endpoint = self.api_endpoint.lstrip("/")
        return f"{base}/{endpoint}"

    def get_full_endpoint_url(self):
        """Get complete endpoint URL."""
        base = self.base_url.rstrip("/")
        endpoint = self.api_endpoint.lstrip("/")
        return f"{base}/{endpoint}"

    def test_connection(self):
        """Test API connection. Updates last_tested_at and last_test_successful."""
        from services.external_api_service import ExternalAPIService

        try:
            service = ExternalAPIService(self)
            service.test_connection()
            self.last_tested_at = timezone.now()
            self.last_test_successful = True
            self.save(update_fields=["last_tested_at", "last_test_successful"])
            return True, "Connection successful"
        except Exception as e:
            self.last_tested_at = timezone.now()
            self.last_test_successful = False
            self.save(update_fields=["last_tested_at", "last_test_successful"])
            return False, str(e)


# ─── Dynamic Permission Layer ────────────────────────────────────────────────

class OrganizationModelAccess(models.Model):
    """
    Layer 1: Controls which models are accessible for a given organization.
    If no record exists for a model → access is ALLOWED by default (open).
    Set is_enabled=False to explicitly block a model for an org.
    """
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name='model_access_rules'
    )
    content_type = models.ForeignKey(
        'contenttypes.ContentType',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text="Select the model to control access for"
    )
    is_enabled = models.BooleanField(default=True)

    @property
    def app_label(self):
        return self.content_type.app_label

    @property
    def model_name(self):
        return self.content_type.model

    class Meta:
        db_table = 'org_model_access'
        constraints = [
            models.UniqueConstraint(
                fields=['organization', 'content_type'],
                condition=models.Q(content_type__isnull=False),
                name='unique_org_content_type'
            )
        ]
        verbose_name = 'Organization Model Access'

    def __str__(self):
        state = 'enabled' if self.is_enabled else 'disabled'
        return f"{self.organization.code} | {self.app_label}.{self.model_name} [{state}]"


class OrganizationGroup(models.Model):
    """
    Wrapper around Django's Group to scope it to an organization.
    is_global=True means the group applies across all organizations.
    """
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name='org_groups'
    )
    group = models.OneToOneField(
        'auth.Group',
        on_delete=models.CASCADE,
        related_name='org_group'
    )
    is_global = models.BooleanField(default=False)

    class Meta:
        db_table = 'org_group'
        verbose_name = 'Organization Group'

    def __str__(self):
        return f"{self.group.name} @ {self.organization.code}"


class Membership(models.Model):
    """
    Layer 2: Scoped role assignment — links a User to a Django Group
    within a specific Organization context.
    A user can belong to different groups in different organizations.
    """
    user = models.ForeignKey(
        'auth.User',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='memberships'
    )
    organization = models.ForeignKey(
        Organization,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='memberships'
    )
    group = models.ForeignKey(
        'auth.Group',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='memberships'
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'org_membership'
        unique_together = ('user', 'organization', 'group')
        verbose_name = 'Membership'

    def __str__(self):
        return f"{self.user.username} @ {self.organization.code} → {self.group.name}"
