from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.utils.timezone import now, timedelta
import random
import string


# ------------------------------------------------------------------ #
# CHOICES & CONSTANTS
# ------------------------------------------------------------------ #
SECTOR_CHOICES = [
    ('hotel',       'Hotel / Guest House'),
    ('hospital',    'Hospital / Clinic'),
    ('school',      'School / College'),
    ('zoo',         'Zoo / Wildlife Park'),
    ('museum',      'Museum / Heritage Site'),
    ('corporate',   'Corporate Office'),
    ('coworking',   'Co-working Space'),
    ('event',       'Event / Conference Venue'),
    ('government',  'Government Office'),
    ('religious',   'Religious Place'),
    ('sports',      'Sports Complex / Stadium'),
    ('factory',     'Factory / Warehouse'),
    ('mall',        'Shopping Mall'),
    ('other',       'Other'),
]

PLAN_CHOICES = [
    ('trial',      'Free Trial'),
    ('starter',    'Starter'),
    ('business',   'Business'),
    ('enterprise', 'Enterprise'),
]

PLAN_LIMITS = {
    'trial':      {'verifications': 50,  'users': 2,  'export': False, 'price': 0},
    'starter':    {'verifications': 100, 'users': 2,  'export': False, 'price': 999},
    'business':   {'verifications': 500, 'users': 5,  'export': True,  'price': 2999},
    'enterprise': {'verifications': -1,  'users': -1, 'export': True,  'price': 7999},
}

TRIAL_DAYS  = 15
GST_PERCENT = 18


# ------------------------------------------------------------------ #
# VERIFICATION FIELDS — the UIDAI claims a client can choose to
# request. Maps each choosable field to its bit position (1-indexed)
# in the 42-bit "sc" bitmap from the UIDAI Aadhaar App Intent spec.
# ------------------------------------------------------------------ #
VERIFICATION_FIELD_BITS = {
    'residentImage': 5,
    'residentName':  6,
    'ageAbove18':    8,
    'dob':           12,
    'gender':        13,
    'careOf':        14,
    'pincode':       34,
    'address':       35,
    'mobile':        37,
    'maskedMobile':  38,
    'email':         39,
    'maskedEmail':   40,
}

VERIFICATION_FIELD_LABELS = {
    'residentImage': 'Photo',
    'residentName':  'Full Name',
    'ageAbove18':    'Age above 18 (yes/no)',
    'dob':           'Date of Birth',
    'gender':        'Gender',
    'careOf':        'Care of (C/O)',
    'pincode':       'PIN Code',
    'address':       'Full Address',
    'mobile':        'Mobile Number',
    'maskedMobile':  'Masked Mobile Number',
    'email':         'Email Address',
    'maskedEmail':   'Masked Email Address',
}

VERIFICATION_FIELD_CHOICES = list(VERIFICATION_FIELD_BITS.keys())


def default_verification_fields():
    """Fixed standard field set requested from UIDAI for every client —
    clients no longer choose their own; this is the platform default."""
    return ['residentImage', 'residentName', 'dob', 'gender', 'address', 'mobile']


# ------------------------------------------------------------------ #
# PLATFORM SETTINGS (singleton) — the one registered OVSE for the
# whole platform. All client callback URLs are generated from this
# base + their own slug, so UIDAI always calls back into Avighna's
# own system no matter which client it is.
# ------------------------------------------------------------------ #
class PlatformSettings(models.Model):
    ovse_base_url = models.URLField(
        default='https://www.as4.in',
        help_text="Registered OVSE base domain, e.g. https://www.as4.in "
                  "The callback URL for every org is <this>/verify-response/ "
                  "(same fixed URL for all clients, registered once with UIDAI)."
    )

    # Single shared UIDAI OVSE credentials used for every client on the
    # platform. Individual clients no longer register their own AC/SA
    # code or keys with UIDAI — Avighna is the one registered OVSE.
    ac_code     = models.CharField(max_length=100, blank=True, help_text="Avighna's UIDAI-issued AC (Auth Client) Code")
    sa_code     = models.CharField(max_length=100, blank=True, help_text="Avighna's UIDAI-issued SA (Sub-AUA) Code")
    private_key = models.TextField(blank=True, help_text="Avighna's RSA private key used to sign every client's JWT")
    public_key  = models.TextField(blank=True)

    # Payment gateway control — when disabled, clients can only activate
    # a plan via coupon code (100% discount codes given manually by the
    # administrator); the Cashfree pay button is hidden entirely.
    payment_gateway_enabled = models.BooleanField(default=True)
    support_email = models.EmailField(
        default='support@avighnasolution.in',
        help_text="Shown to clients when payment gateway is disabled, so they know who to contact for activation."
    )

    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"Platform Settings ({self.ovse_base_url})"

    def save(self, *args, **kwargs):
        self.pk = 1  # enforce singleton
        super().save(*args, **kwargs)

    def is_uidai_ready(self):
        return bool(self.ac_code and self.sa_code and self.private_key)

    @classmethod
    def get_solo(cls):
        obj, _ = cls.objects.get_or_create(pk=1)
        return obj

    class Meta:
        verbose_name = 'Platform Settings'
        verbose_name_plural = 'Platform Settings'


# ------------------------------------------------------------------ #
# ORGANIZATION
# ------------------------------------------------------------------ #
class Organization(models.Model):
    name     = models.CharField(max_length=200)
    slug     = models.SlugField(max_length=100, unique=True)
    sector   = models.CharField(max_length=50, choices=SECTOR_CHOICES, default='other')

    email    = models.EmailField(unique=True)
    phone    = models.CharField(max_length=15, blank=True)
    address  = models.TextField(blank=True)
    city     = models.CharField(max_length=100, blank=True)
    state    = models.CharField(max_length=100, default='Uttar Pradesh')

    # UIDAI (legacy per-org fields — kept for backward compatibility with
    # existing data, no longer used. Avighna is now the single OVSE;
    # credentials live centrally on PlatformSettings.)
    ac_code          = models.CharField(max_length=100, blank=True)
    sa_code          = models.CharField(max_length=100, blank=True)
    private_key      = models.TextField(blank=True)
    public_key       = models.TextField(blank=True)
    callback_url     = models.URLField(blank=True)
    uidai_setup_done = models.BooleanField(default=False)

    # Which UIDAI claims this org wants verified/collected — chosen by
    # the client themselves (e.g. at registration or from settings).
    verification_fields = models.JSONField(default=default_verification_fields, blank=True)

    # Library/Coaching module — default monthly fee for students unless
    # a student has their own custom monthly_fee set.
    default_monthly_fee = models.IntegerField(default=0)

    # Plan & Trial
    plan           = models.CharField(max_length=20, choices=PLAN_CHOICES, default='trial')
    plan_start     = models.DateTimeField(null=True, blank=True)
    plan_end       = models.DateTimeField(null=True, blank=True)
    trial_start    = models.DateTimeField(null=True, blank=True)
    trial_end      = models.DateTimeField(null=True, blank=True)
    is_trial       = models.BooleanField(default=True)
    trial_expired  = models.BooleanField(default=False)

    verif_count    = models.IntegerField(default=0)
    verif_reset_at = models.DateTimeField(null=True, blank=True)
    topup_count    = models.IntegerField(default=0)  # extra verifications from top-ups

    is_active  = models.BooleanField(default=True)
    is_verified= models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.name} ({self.slug})"

    def save(self, *args, **kwargs):
        if self.slug:
            self.callback_url = self.build_callback_url()
        super().save(*args, **kwargs)

    def build_callback_url(self):
        """The single, fixed OVSE callback URL already registered with
        UIDAI — identical for every org (e.g. https://www.as4.in/verify-response/).
        UIDAI does not know or care which client a request is for; that
        is resolved on our side from the TxnID in the callback body."""
        base = PlatformSettings.get_solo().ovse_base_url.rstrip('/')
        return f"{base}/verify-response/"

    def build_sc_bitmap(self):
        """Builds the 42-bit UIDAI 'sc' bitmap string. Verification fields
        are now a fixed platform standard for every client (not client-
        configurable), so this always uses the same default set."""
        fields = default_verification_fields()
        bits   = ['0'] * 42
        for field in fields:
            pos = VERIFICATION_FIELD_BITS.get(field)
            if pos:
                bits[pos - 1] = '1'
        return ''.join(bits)

    def get_verification_field_labels(self):
        fields = default_verification_fields()
        return [VERIFICATION_FIELD_LABELS.get(f, f) for f in fields]

    def start_trial(self):
        self.is_trial   = True
        self.trial_start= now()
        self.trial_end  = now() + timedelta(days=TRIAL_DAYS)
        self.plan       = 'trial'
        self.plan_start = now()
        self.save()

    def trial_days_remaining(self):
        if not self.trial_end:
            return 0
        return max(0, (self.trial_end - now()).days)

    def is_trial_expired(self):
        if not self.is_trial:
            return False
        return not self.trial_end or now() > self.trial_end

    def check_and_expire_trial(self):
        if self.is_trial and not self.trial_expired and self.is_trial_expired():
            self.trial_expired = True
            self.is_active     = False
            self.save(update_fields=['trial_expired', 'is_active'])
            return True
        return False

    def get_plan_limit(self):
        return PLAN_LIMITS.get(self.plan, PLAN_LIMITS['trial'])

    def can_verify(self):
        if self.trial_expired:
            return False
        limit = self.get_plan_limit()['verifications']
        if limit == -1:
            return True
        total_allowed = limit + self.topup_count
        return self.verif_count < total_allowed

    def verifications_remaining(self):
        if self.trial_expired:
            return 0
        limit = self.get_plan_limit()['verifications']
        if limit == -1:
            return 999999
        total_allowed = limit + self.topup_count
        return max(0, total_allowed - self.verif_count)

    def add_topup(self, extra_verifications):
        self.topup_count += extra_verifications
        self.save(update_fields=['topup_count'])

    def can_export(self):
        return not self.trial_expired and self.get_plan_limit()['export']

    def increment_verif_count(self):
        if self.verif_reset_at and now() > self.verif_reset_at:
            self.verif_count    = 0
            self.verif_reset_at = now() + timedelta(days=30)
            self.save(update_fields=['verif_count', 'verif_reset_at'])
            return
        self.verif_count += 1
        self.save(update_fields=['verif_count'])

    def activate_plan(self, plan_name):
        self.plan          = plan_name
        self.is_trial      = False
        self.trial_expired = False
        self.is_active     = True
        self.plan_start    = now()
        self.plan_end      = now() + timedelta(days=30)
        self.verif_count   = 0
        self.verif_reset_at= now() + timedelta(days=30)
        self.save()

    class Meta:
        ordering = ['-created_at']
        verbose_name = 'Organization'
        verbose_name_plural = 'Organizations'


# ------------------------------------------------------------------ #
# SUBSCRIPTION
# ------------------------------------------------------------------ #
class Subscription(models.Model):
    STATUS = [
        ('trial',    'Free Trial'),
        ('active',   'Active'),
        ('expired',  'Expired'),
        ('cancelled','Cancelled'),
    ]
    org        = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='subscriptions')
    plan       = models.CharField(max_length=20, choices=PLAN_CHOICES)
    status     = models.CharField(max_length=20, choices=STATUS, default='trial')
    amount     = models.IntegerField(default=0)
    start_date = models.DateTimeField()
    end_date   = models.DateTimeField()
    notes      = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.org.name} — {self.plan} ({self.status})"

    class Meta:
        ordering = ['-created_at']


# ------------------------------------------------------------------ #
# COUPON CODE
# ------------------------------------------------------------------ #
DISCOUNT_TYPE_CHOICES = [
    ('percent', 'Percentage Discount'),
    ('flat',    'Flat Amount Discount'),
    ('free',    'Full Waiver (100% Free)'),
]

class Coupon(models.Model):
    code             = models.CharField(max_length=50, unique=True)
    description      = models.CharField(max_length=200, blank=True)
    discount_type    = models.CharField(max_length=20, choices=DISCOUNT_TYPE_CHOICES, default='percent')
    discount_value   = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    applicable_plans = models.JSONField(default=list, blank=True)  # empty = all plans
    max_uses         = models.IntegerField(default=0)              # 0 = unlimited
    used_count       = models.IntegerField(default=0)
    valid_from       = models.DateTimeField(null=True, blank=True)
    valid_until      = models.DateTimeField(null=True, blank=True)
    is_active        = models.BooleanField(default=True)
    created_at       = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.code} ({self.get_discount_type_display()})"

    def is_valid(self, plan=None):
        if not self.is_active:
            return False, "This coupon is inactive."
        if self.max_uses > 0 and self.used_count >= self.max_uses:
            return False, "This coupon has reached its usage limit."
        if self.valid_from and now() < self.valid_from:
            return False, "This coupon is not yet active."
        if self.valid_until and now() > self.valid_until:
            return False, "This coupon has expired."
        if plan and self.applicable_plans and plan not in self.applicable_plans:
            return False, f"This coupon is not applicable for the {plan} plan."
        return True, "Valid"

    def calculate_discount(self, original_amount):
        if self.discount_type == 'free':
            return original_amount
        elif self.discount_type == 'percent':
            return min(round((original_amount * float(self.discount_value)) / 100, 2), original_amount)
        elif self.discount_type == 'flat':
            return min(float(self.discount_value), original_amount)
        return 0

    def apply(self):
        self.used_count += 1
        self.save(update_fields=['used_count'])

    class Meta:
        ordering = ['-created_at']


# ------------------------------------------------------------------ #
# PAYMENT RECORD
# ------------------------------------------------------------------ #
PAYMENT_STATUS_CHOICES = [
    ('pending',  'Pending'),
    ('success',  'Success'),
    ('failed',   'Failed'),
    ('refunded', 'Refunded'),
]

class Payment(models.Model):
    org               = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='payments')
    plan              = models.CharField(max_length=20, choices=PLAN_CHOICES)
    original_amount   = models.IntegerField()
    discount_amount   = models.IntegerField(default=0)
    gst_amount        = models.IntegerField(default=0)
    final_amount      = models.IntegerField()
    coupon            = models.ForeignKey(Coupon, on_delete=models.SET_NULL, null=True, blank=True)
    gateway           = models.CharField(max_length=50, default='cashfree')
    gateway_order_id  = models.CharField(max_length=200, blank=True)
    gateway_payment_id= models.CharField(max_length=200, blank=True)
    gateway_signature = models.CharField(max_length=500, blank=True)
    status            = models.CharField(max_length=20, choices=PAYMENT_STATUS_CHOICES, default='pending')
    payment_method    = models.CharField(max_length=50, blank=True)
    notes             = models.TextField(blank=True)
    created_at        = models.DateTimeField(auto_now_add=True)
    updated_at        = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.org.name} — {self.plan} — ₹{self.final_amount} — {self.status}"

    class Meta:
        ordering = ['-created_at']


# ------------------------------------------------------------------ #
# OTP VERIFICATION
# ------------------------------------------------------------------ #
class OTPVerification(models.Model):
    email      = models.EmailField()
    otp        = models.CharField(max_length=6)
    form_data  = models.JSONField()
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()
    is_used    = models.BooleanField(default=False)

    def __str__(self):
        return f"{self.email} — {self.otp}"

    def is_expired(self):
        return now() > self.expires_at

    @classmethod
    def generate_otp(cls):
        return ''.join(random.choices(string.digits, k=6))

    @classmethod
    def create_for_email(cls, email, form_data):
        cls.objects.filter(email=email, is_used=False).delete()
        return cls.objects.create(
            email      = email,
            otp        = cls.generate_otp(),
            form_data  = form_data,
            expires_at = now() + timedelta(minutes=10),
        )

    class Meta:
        ordering = ['-created_at']
        verbose_name = 'OTP Verification'


# ------------------------------------------------------------------ #
# USER
# ------------------------------------------------------------------ #
class UserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Email is required.')
        email = self.normalize_email(email)
        user  = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('role', 'super_admin')
        return self.create_user(email, password, **extra_fields)


ROLE_CHOICES = [
    ('super_admin', 'Super Admin'),
    ('org_admin',   'Org Admin'),
    ('org_staff',   'Org Staff'),
]

class User(AbstractBaseUser, PermissionsMixin):
    email      = models.EmailField(unique=True)
    name       = models.CharField(max_length=150)
    org        = models.ForeignKey(Organization, on_delete=models.CASCADE,
                   null=True, blank=True, related_name='users')
    role       = models.CharField(max_length=20, choices=ROLE_CHOICES, default='org_staff')
    is_active  = models.BooleanField(default=True)
    is_staff   = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD  = 'email'
    REQUIRED_FIELDS = ['name']
    objects = UserManager()

    def __str__(self):
        return f"{self.name} <{self.email}>"

    @property
    def is_super_admin(self): return self.role == 'super_admin'
    @property
    def is_org_admin(self):   return self.role == 'org_admin'
    @property
    def is_org_staff(self):   return self.role == 'org_staff'

    class Meta:
        ordering = ['-created_at']


# ------------------------------------------------------------------ #
# KNOWN DEVICE
# ------------------------------------------------------------------ #
class KnownDevice(models.Model):
    user           = models.ForeignKey(User, on_delete=models.CASCADE, related_name='known_devices')
    device_id      = models.CharField(max_length=64)   # SHA256 fingerprint stored in cookie
    device_label   = models.CharField(max_length=200, blank=True)  # e.g. "Chrome on Windows"
    ip_address     = models.GenericIPAddressField(null=True, blank=True)
    last_used_at   = models.DateTimeField(auto_now=True)
    created_at     = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.user.email} — {self.device_label}"

    @classmethod
    def is_known(cls, user, device_id):
        return cls.objects.filter(user=user, device_id=device_id).exists()

    @classmethod
    def remember(cls, user, device_id, label, ip):
        obj, _ = cls.objects.get_or_create(
            user=user, device_id=device_id,
            defaults={'device_label': label, 'ip_address': ip}
        )
        obj.device_label = label
        obj.ip_address   = ip
        obj.save(update_fields=['device_label', 'ip_address', 'last_used_at'])
        return obj

    class Meta:
        ordering        = ['-last_used_at']
        unique_together = [('user', 'device_id')]


# ------------------------------------------------------------------ #
# LOGIN OTP
# ------------------------------------------------------------------ #
class LoginOTP(models.Model):
    user       = models.ForeignKey(User, on_delete=models.CASCADE, related_name='login_otps')
    otp        = models.CharField(max_length=6)
    device_id  = models.CharField(max_length=64)   # fingerprint of the new device
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()
    is_used    = models.BooleanField(default=False)

    def __str__(self):
        return f"{self.user.email} — {self.otp}"

    def is_expired(self):
        return now() > self.expires_at

    @classmethod
    def create(cls, user, device_id):
        cls.objects.filter(user=user, is_used=False).delete()
        otp = ''.join(random.choices(string.digits, k=6))
        return cls.objects.create(
            user       = user,
            otp        = otp,
            device_id  = device_id,
            expires_at = now() + timedelta(minutes=10),
        )

    class Meta:
        ordering = ['-created_at']


# ------------------------------------------------------------------ #
# TOP-UP PACKS
# ------------------------------------------------------------------ #
TOPUP_PACKS = {
    'small':  {'verifications': 100, 'price': 199,  'label': 'Small Pack'},
    'medium': {'verifications': 300, 'price': 499,  'label': 'Medium Pack'},
    'large':  {'verifications': 500, 'price': 799,  'label': 'Large Pack'},
}

class TopUpOrder(models.Model):
    STATUS = [
        ('pending', 'Pending'),
        ('success', 'Success'),
        ('failed',  'Failed'),
    ]
    org               = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='topups')
    pack              = models.CharField(max_length=20)   # small/medium/large
    verifications     = models.IntegerField()
    original_amount   = models.IntegerField()
    gst_amount        = models.IntegerField(default=0)
    final_amount      = models.IntegerField()
    coupon            = models.ForeignKey('Coupon', on_delete=models.SET_NULL, null=True, blank=True)
    discount_amount   = models.IntegerField(default=0)
    gateway           = models.CharField(max_length=50, default='cashfree')
    gateway_order_id  = models.CharField(max_length=200, blank=True)
    gateway_payment_id= models.CharField(max_length=200, blank=True)
    status            = models.CharField(max_length=20, choices=STATUS, default='pending')
    created_at        = models.DateTimeField(auto_now_add=True)
    updated_at        = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.org.name} — {self.pack} — ₹{self.final_amount} — {self.status}"

    class Meta:
        ordering = ['-created_at']
