from django.contrib.auth.models import AbstractUser
from django.db import models
import json
from apps.accounts.fields import WebPImageField


class User(AbstractUser):
    """
    Extended user model.

    Role hierarchy (highest → lowest):
      system_admin    — YOUR COMPANY platform admin (head of team)
      super_admin     — Business owner; full control over their brand
      business_admin  — Manage products, services, orders, operations
      finance_admin   — View payments, invoices, financial reports
      support_agent   — View customers, orders and support cases
      content_manager — Create/publish portfolio, insights, pages, media
      analyst         — Read-only: dashboards, reports, performance
    """
    ROLE_SYSTEM_ADMIN    = 'system_admin'
    ROLE_SUPER_ADMIN     = 'super_admin'
    ROLE_BUSINESS_ADMIN  = 'business_admin'
    ROLE_FINANCE_ADMIN   = 'finance_admin'
    ROLE_SUPPORT_AGENT   = 'support_agent'
    ROLE_CONTENT_MANAGER = 'content_manager'
    ROLE_ANALYST         = 'analyst'

    ROLE_CHOICES = [
        (ROLE_SYSTEM_ADMIN,    'System Administrator'),
        (ROLE_SUPER_ADMIN,     'Super Admin'),
        (ROLE_BUSINESS_ADMIN,  'Business Admin'),
        (ROLE_FINANCE_ADMIN,   'Finance Admin'),
        (ROLE_SUPPORT_AGENT,   'Support Agent'),
        (ROLE_CONTENT_MANAGER, 'Content Manager'),
        (ROLE_ANALYST,         'Analyst'),
    ]

    # Badge colours used across CMS templates
    ROLE_COLORS = {
        ROLE_SYSTEM_ADMIN:    '#16517b',
        ROLE_SUPER_ADMIN:     '#e8b400',
        ROLE_BUSINESS_ADMIN:  '#6D28D9',
        ROLE_FINANCE_ADMIN:   '#059669',
        ROLE_SUPPORT_AGENT:   '#D97706',
        ROLE_CONTENT_MANAGER: '#2563EB',
        ROLE_ANALYST:         '#64748B',
    }

    role = models.CharField(max_length=20, choices=ROLE_CHOICES, default=ROLE_ANALYST)

    def save(self, *args, **kwargs):
        # Django superusers always get system_admin
        if self.is_superuser and self.role == self.ROLE_ANALYST:
            self.role = self.ROLE_SYSTEM_ADMIN
        super().save(*args, **kwargs)
    avatar = WebPImageField(upload_to='avatars/', blank=True, null=True)
    phone = models.CharField(max_length=30, blank=True)
    company = models.CharField(max_length=120, blank=True)
    position = models.CharField(max_length=120, blank=True)

    # FAB Club OAuth fields
    fab_uid = models.CharField(max_length=120, unique=True, null=True, blank=True)
    fab_token = models.TextField(blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'User'
        verbose_name_plural = 'Users'
        ordering = ['-created_at']

    def __str__(self):
        return f'{self.get_full_name() or self.username} ({self.get_role_display()})'

    @property
    def is_cms_user(self):
        """All users in this system are CMS users."""
        return True

    @property
    def is_system_admin(self):
        return self.role == self.ROLE_SYSTEM_ADMIN

    @property
    def is_super_admin(self):
        """system_admin and super_admin both carry top-level privileges."""
        return self.role in (self.ROLE_SYSTEM_ADMIN, self.ROLE_SUPER_ADMIN)

    @property
    def is_admin(self):
        """Roles that can manage other users and operational settings."""
        return self.role in (
            self.ROLE_SYSTEM_ADMIN,
            self.ROLE_SUPER_ADMIN,
            self.ROLE_BUSINESS_ADMIN,
        )

    @property
    def two_fa(self):
        try:
            return self.twofa
        except TwoFactorAuth.DoesNotExist:
            return None

    @property
    def has_2fa(self):
        fa = self.two_fa
        return fa is not None and fa.is_enabled


class TwoFactorAuth(models.Model):
    """
    One row per user. Stores their chosen 2FA method and secrets.
    Multiple methods can be enrolled — user picks which to use at login.
    """
    METHOD_TOTP = 'totp'
    METHOD_EMAIL = 'email'
    METHOD_SMS = 'sms'

    METHOD_CHOICES = [
        (METHOD_TOTP, 'Authenticator App (Google / Microsoft)'),
        (METHOD_EMAIL, 'Email OTP'),
        (METHOD_SMS, 'SMS OTP'),
    ]

    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='twofa')
    is_enabled = models.BooleanField(default=False)

    # Active method shown at login
    preferred_method = models.CharField(
        max_length=10, choices=METHOD_CHOICES, default=METHOD_EMAIL
    )

    # TOTP
    totp_secret = models.CharField(max_length=64, blank=True)
    totp_confirmed = models.BooleanField(default=False)  # User scanned + verified QR

    # Email
    email_confirmed = models.BooleanField(default=False)

    # SMS
    sms_phone = models.CharField(max_length=30, blank=True)
    sms_confirmed = models.BooleanField(default=False)

    # Backup codes — stored as JSON list of {hash, used}
    backup_codes = models.TextField(blank=True, default='[]')

    # Audit
    last_used_at = models.DateTimeField(null=True, blank=True)
    last_used_method = models.CharField(max_length=10, blank=True)
    enrolled_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = '2FA Configuration'

    def __str__(self):
        return f'2FA for {self.user.username} — {self.get_preferred_method_display()}'

    @property
    def backup_codes_list(self):
        try:
            return json.loads(self.backup_codes)
        except Exception:
            return []

    @backup_codes_list.setter
    def backup_codes_list(self, value):
        self.backup_codes = json.dumps(value)

    @property
    def available_methods(self):
        """Returns list of confirmed/available methods for this user."""
        methods = []
        if self.totp_confirmed:
            methods.append(self.METHOD_TOTP)
        if self.email_confirmed:
            methods.append(self.METHOD_EMAIL)
        if self.sms_confirmed:
            methods.append(self.METHOD_SMS)
        return methods

    @property
    def unused_backup_count(self):
        return sum(1 for c in self.backup_codes_list if not c.get('used'))
