from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.contrib.auth.hashers import make_password, check_password
from django.core.exceptions import ValidationError
from django.utils import timezone

from apps.core.models import BaseModel


class UserManager(BaseUserManager):
    def create_user(self, email=None, mobile=None, password=None, login_pin=None, **extra_fields):
        if not email and not mobile:
            raise ValueError('Users must have an email or mobile')
        email = self.normalize_email(email) if email else None
        if mobile:
            mobile = mobile.strip()
        referral_code = extra_fields.get('referral_code')
        # If referral_code not provided, attempt to generate a unique one with retries
        if not referral_code:
            import uuid
            from django.db import IntegrityError

            max_attempts = 5
            last_exc = None
            for _ in range(max_attempts):
                candidate = str(uuid.uuid4())[:10].replace('-', '').upper()
                extra_fields['referral_code'] = candidate
                user = self.model(email=email, mobile=mobile, **extra_fields)
                if password:
                    user.set_password(password)
                else:
                    user.set_unusable_password()
                if login_pin:
                    user.set_login_pin(login_pin)
                try:
                    user.save(using=self._db)
                    return user
                except IntegrityError as e:
                    # Could be referral_code collision or other unique constraint; retry on referral collisions
                    last_exc = e
                    # If referral_code already exists, try another; otherwise re-raise
                    if self.model.objects.filter(referral_code=candidate).exists():
                        continue
                    raise
            # If we exhausted attempts, raise the last IntegrityError
            if last_exc:
                raise last_exc
        # If referral_code provided in extra_fields, proceed normally
        user = self.model(email=email, mobile=mobile, **extra_fields)
        if password:
            user.set_password(password)
        else:
            user.set_unusable_password()
        if login_pin:
            user.set_login_pin(login_pin)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('auth_provider', 'email')
        return self.create_user(email=email, password=password, **extra_fields)

    def create_mobile_user(self, mobile, login_pin, **extra_fields):
        """Convenience method for mobile-based registration."""
        if not mobile:
            raise ValueError('Mobile number is required')
        if not login_pin:
            raise ValueError('Login PIN is required for mobile users')
        extra_fields.setdefault('auth_provider', 'mobile')
        return self.create_user(mobile=mobile, login_pin=login_pin, **extra_fields)

    def create_google_user(self, email, google_id, **extra_fields):
        """Convenience method for Google OAuth registration."""
        if not google_id:
            raise ValueError('Google ID is required for Google users')
        extra_fields.setdefault('auth_provider', 'google')
        extra_fields['google_id'] = google_id
        extra_fields['is_email_verified'] = True
        return self.create_user(email=email, **extra_fields)


class User(BaseModel, AbstractBaseUser, PermissionsMixin):
    AUTH_PROVIDER_CHOICES = (
        ('mobile', 'Mobile'),
        ('google', 'Google'),
        ('email', 'Email'),
        ('apple', 'Apple'),
    )
    
    email = models.EmailField(max_length=255, unique=True, null=True, blank=True)
    mobile = models.CharField(max_length=30, unique=True, null=True, blank=True)
    first_name = models.CharField(max_length=150, blank=True)
    last_name = models.CharField(max_length=150, blank=True)
    auth_provider = models.CharField(max_length=20, choices=AUTH_PROVIDER_CHOICES, default='mobile')
    google_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
    google_avatar_url = models.URLField(max_length=500, null=True, blank=True)
    profile_image = models.ImageField(upload_to='profiles/', null=True, blank=True)
    login_pin = models.CharField(max_length=128, null=True, blank=True)
    must_change_login_pin = models.BooleanField(default=False, db_index=True)
    is_email_verified = models.BooleanField(default=False)
    is_mobile_verified = models.BooleanField(default=False)
    date_joined = models.DateTimeField(default=timezone.now)

    # OTP fields (not integrated with external providers)
    email_otp = models.CharField(max_length=10, null=True, blank=True)
    mobile_otp = models.CharField(max_length=10, null=True, blank=True)
    otp_expiry = models.DateTimeField(null=True, blank=True)

    is_staff = models.BooleanField(default=False)
    role = models.CharField(max_length=50, default='PUBLIC', db_index=True)

    objects = UserManager()
    # Onboarding fields
    pan_number = models.CharField(max_length=20, unique=True, null=True, blank=True)
    referral_code = models.CharField(max_length=10, unique=True, null=True, blank=True, db_index=True)
    referred_by = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL, related_name='referrals')
    pincode = models.ForeignKey('geo.Pincode', null=True, blank=True, on_delete=models.SET_NULL)
    location = models.CharField(max_length=255, null=True, blank=True)
    address_line1 = models.CharField(max_length=255, null=True, blank=True)
    address_line2 = models.CharField(max_length=255, null=True, blank=True)
    city = models.CharField(max_length=120, null=True, blank=True)
    state = models.CharField(max_length=120, null=True, blank=True)
    country = models.CharField(max_length=120, null=True, blank=True, default='India')
    occupation = models.ForeignKey('Occupation', null=True, blank=True, on_delete=models.SET_NULL, related_name='users')
    date_of_birth = models.DateField(null=True, blank=True)
    is_onboarding_complete = models.BooleanField(default=False)
    accept_terms = models.BooleanField(default=False)
    # Invoice / billing extras
    gst_number = models.CharField(max_length=15, null=True, blank=True)
    company_name = models.CharField(max_length=255, null=True, blank=True)
    billing_postcode = models.CharField(max_length=10, null=True, blank=True)

    class Meta:
        indexes = [
            models.Index(fields=['referral_code']),
            models.Index(fields=['mobile']),
        ]

    def save(self, *args, **kwargs):
        if self.pan_number:
            self.pan_number = self.pan_number.upper().strip()
        if self.mobile:
            self.mobile = self.mobile.strip()
        super().save(*args, **kwargs)

    USERNAME_FIELD = 'mobile'
    REQUIRED_FIELDS = ['email']

    def set_login_pin(self, raw_pin):
        """Hash and store the login PIN."""
        self.login_pin = make_password(raw_pin)

    def check_login_pin(self, raw_pin):
        """Verify a raw PIN against the stored hash."""
        if not self.login_pin:
            return False
        return check_password(raw_pin, self.login_pin)

    def clean(self):
        super().clean()
        if self.auth_provider == 'mobile' and not self.login_pin:
            raise ValidationError({'login_pin': 'Login PIN is required for mobile users.'})
        if self.auth_provider == 'google' and not self.google_id:
            raise ValidationError({'google_id': 'Google ID is required for Google users.'})

    def __str__(self):
        return self.mobile or self.email or str(self.id)


class UserShippingAddress(BaseModel):
    """User-managed shipping addresses (multiple allowed)."""
    user = models.ForeignKey(
        'accounts.User',
        on_delete=models.CASCADE,
        related_name='shipping_addresses',
    )
    label = models.CharField(max_length=100, blank=True, help_text="e.g. Home, Office")
    name = models.CharField(max_length=255)
    address_line1 = models.CharField(max_length=255)
    address_line2 = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=120)
    state = models.CharField(max_length=120)
    country = models.CharField(max_length=120, default='India')
    postcode = models.CharField(max_length=10, blank=True)
    phone = models.CharField(max_length=20, blank=True)
    is_default = models.BooleanField(default=False)

    class Meta:
        ordering = ['-is_default', 'label']
        verbose_name = 'User Shipping Address'
        verbose_name_plural = 'User Shipping Addresses'

    def __str__(self):
        return f"{self.user_id} - {self.label or self.name}"


class EmailVerificationToken(BaseModel):
    """Temporary store for email verification token during registration."""
    email = models.EmailField(db_index=True)
    token = models.CharField(max_length=64, unique=True)
    expires_at = models.DateTimeField()

    class Meta:
        verbose_name = 'Email Verification Token'
        verbose_name_plural = 'Email Verification Tokens'

    @property
    def is_expired(self):
        return timezone.now() > self.expires_at

    def __str__(self):
        return f'{self.email} - {self.token}'


class Occupation(models.Model):
    name = models.CharField(max_length=64, unique=True)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return self.name


class Role(models.Model):
    code = models.CharField(max_length=50, unique=True, db_index=True)
    name = models.CharField(max_length=100, unique=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ['name']

    def save(self, *args, **kwargs):
        if self.code:
            self.code = self.code.strip().upper()
        if self.name:
            self.name = self.name.strip()
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name


class RolePageAccess(models.Model):
    role_code = models.CharField(max_length=50, db_index=True)
    page_key = models.CharField(max_length=100, db_index=True)
    is_allowed = models.BooleanField(default=True)

    class Meta:
        ordering = ['role_code', 'page_key']
        unique_together = ('role_code', 'page_key')

    def save(self, *args, **kwargs):
        if self.role_code:
            self.role_code = self.role_code.strip().upper()
        if self.page_key:
            self.page_key = self.page_key.strip()
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.role_code} -> {self.page_key} ({'allow' if self.is_allowed else 'deny'})"


class UserKYC(BaseModel):
    DOC_STATUS_NOT_UPLOADED = 'not_uploaded'
    DOC_STATUS_PENDING = 'pending'
    DOC_STATUS_APPROVED = 'approved'
    DOC_STATUS_REJECTED = 'rejected'

    DOC_STATUS_CHOICES = (
        (DOC_STATUS_NOT_UPLOADED, 'Not Uploaded'),
        (DOC_STATUS_PENDING, 'Pending'),
        (DOC_STATUS_APPROVED, 'Approved'),
        (DOC_STATUS_REJECTED, 'Rejected'),
    )

    OVERALL_DRAFT = 'draft'
    OVERALL_UNDER_REVIEW = 'under_review'
    OVERALL_APPROVED = 'approved'
    OVERALL_REJECTED = 'rejected'

    OVERALL_STATUS_CHOICES = (
        (OVERALL_DRAFT, 'Draft'),
        (OVERALL_UNDER_REVIEW, 'Under Review'),
        (OVERALL_APPROVED, 'Approved'),
        (OVERALL_REJECTED, 'Rejected'),
    )

    user = models.OneToOneField('accounts.User', on_delete=models.CASCADE, related_name='kyc_profile')
    pan_card_image = models.ImageField(upload_to='kyc/pan/', null=True, blank=True)
    aadhaar_front_image = models.ImageField(upload_to='kyc/aadhaar/', null=True, blank=True)
    aadhaar_back_image = models.ImageField(upload_to='kyc/aadhaar/', null=True, blank=True)
    bank_proof_image = models.ImageField(upload_to='kyc/bank/', null=True, blank=True)

    pan_status = models.CharField(max_length=20, choices=DOC_STATUS_CHOICES, default=DOC_STATUS_NOT_UPLOADED, db_index=True)
    aadhaar_status = models.CharField(max_length=20, choices=DOC_STATUS_CHOICES, default=DOC_STATUS_NOT_UPLOADED, db_index=True)
    bank_status = models.CharField(max_length=20, choices=DOC_STATUS_CHOICES, default=DOC_STATUS_NOT_UPLOADED, db_index=True)

    pan_review_note = models.CharField(max_length=500, blank=True, default='')
    aadhaar_review_note = models.CharField(max_length=500, blank=True, default='')
    bank_review_note = models.CharField(max_length=500, blank=True, default='')

    overall_status = models.CharField(max_length=20, choices=OVERALL_STATUS_CHOICES, default=OVERALL_DRAFT, db_index=True)
    submitted_at = models.DateTimeField(null=True, blank=True)
    reviewed_at = models.DateTimeField(null=True, blank=True)
    reviewed_by = models.ForeignKey('accounts.User', null=True, blank=True, on_delete=models.SET_NULL, related_name='reviewed_kyc_profiles')

    class Meta:
        ordering = ['-updated_at']

    @property
    def has_all_required_documents(self):
        return bool(self.pan_card_image and self.aadhaar_front_image and self.aadhaar_back_image and self.bank_proof_image)

    def __str__(self):
        return f"KYC {self.user_id} ({self.overall_status})"
