import hashlib
import secrets
from datetime import timedelta
from django.conf import settings
from django.contrib.auth.models import AbstractUser,BaseUserManager
from django.db import models
from django.utils import timezone
class UserManager(BaseUserManager):
 def create_user(self,phone_number,password=None,**extra):
  if not phone_number: raise ValueError('Phone number is required')
  user=self.model(phone_number=phone_number,username=phone_number,**extra); user.set_unusable_password() if not password else user.set_password(password); user.save(using=self._db); return user
 def create_superuser(self,phone_number,password=None,**extra):
  extra.update(is_staff=True,is_superuser=True,is_active=True); return self.create_user(phone_number,password,**extra)
class User(AbstractUser):
 class MemberType(models.TextChoices): CURRENT='CURRENT','Current student'; ALUMNI='ALUMNI','Alumni'; PATRON='PATRON','Patron'; BOARD='BOARD','Church board'
 phone_number=models.CharField(max_length=20,unique=True); email=models.EmailField(blank=True); first_name=models.CharField(max_length=80); last_name=models.CharField(max_length=80); other_names=models.CharField(max_length=100,blank=True); gender=models.CharField(max_length=20,blank=True); date_of_birth=models.DateField(null=True,blank=True); country=models.ForeignKey('core.Country',null=True,blank=True,on_delete=models.SET_NULL); campus=models.ForeignKey('core.Campus',null=True,blank=True,on_delete=models.SET_NULL,related_name='members'); member_type=models.CharField(max_length=20,choices=MemberType.choices,default=MemberType.CURRENT); graduation_year=models.PositiveIntegerField(null=True,blank=True); profession=models.CharField(max_length=150,blank=True); employer=models.CharField(max_length=180,blank=True); job_title=models.CharField(max_length=150,blank=True); bio=models.TextField(blank=True); profile_completed=models.BooleanField(default=False)
 USERNAME_FIELD='phone_number'; REQUIRED_FIELDS=[]; objects=UserManager()
class EmploymentHistory(models.Model):
 user=models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,related_name='employment_history'); employer=models.CharField(max_length=180); job_title=models.CharField(max_length=150); industry=models.ForeignKey('core.Industry',null=True,blank=True,on_delete=models.SET_NULL,related_name='employment_history'); start_date=models.DateField(); end_date=models.DateField(null=True,blank=True); current=models.BooleanField(default=False); description=models.TextField(blank=True)
 class Meta: ordering=['-current','-start_date']
class OTPChallenge(models.Model):
 phone_number=models.CharField(max_length=20,db_index=True); code=models.CharField(max_length=6); purpose=models.CharField(max_length=20,default='LOGIN'); created_at=models.DateTimeField(auto_now_add=True); expires_at=models.DateTimeField(); attempts=models.PositiveSmallIntegerField(default=0); consumed_at=models.DateTimeField(null=True,blank=True)
 @classmethod
 def issue(cls,phone,purpose='LOGIN'):
  use_dev_code=settings.DEBUG and settings.OTP_PROVIDER == 'console'
  code=settings.OTP_DEV_CODE if use_dev_code else f'{secrets.randbelow(1000000):06d}'
  cls.objects.filter(phone_number=phone,consumed_at__isnull=True).update(consumed_at=timezone.now())
  return cls.objects.create(phone_number=phone,code=code,purpose=purpose,expires_at=timezone.now()+timedelta(minutes=settings.OTP_TTL_MINUTES))
 def valid(self,code): return not self.consumed_at and self.expires_at>timezone.now() and self.attempts<5 and secrets.compare_digest(self.code,code)

class Member(models.Model):
 class Status(models.TextChoices): ACTIVE='ACTIVE','Active'; INACTIVE='INACTIVE','Inactive'; DECEASED='DECEASED','Deceased'
 class AccountStatus(models.TextChoices): PENDING='PENDING','Credentials not sent'; INVITED='INVITED','Invitation sent'; ACTIVE='ACTIVE','Account activated'; DISABLED='DISABLED','Account disabled'
 member_number=models.CharField(max_length=20,unique=True,blank=True)
 user=models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.PROTECT,related_name='member_profile')
 first_name=models.CharField(max_length=80); other_names=models.CharField(max_length=100,blank=True); last_name=models.CharField(max_length=80)
 gender=models.CharField(max_length=20,blank=True); nationality=models.CharField(max_length=100,blank=True)
 country=models.ForeignKey('core.Country',null=True,blank=True,on_delete=models.SET_NULL,related_name='member_records')
 campus=models.ForeignKey('core.Campus',null=True,blank=True,on_delete=models.SET_NULL,related_name='member_records')
 campus_name_raw=models.CharField(max_length=180,blank=True); primary_phone=models.CharField(max_length=20,unique=True); alternate_phone=models.CharField(max_length=20,blank=True)
 church_branch=models.CharField(max_length=150,blank=True); status=models.CharField(max_length=15,choices=Status.choices,default=Status.ACTIVE)
 account_status=models.CharField(max_length=15,choices=AccountStatus.choices,default=AccountStatus.PENDING)
 source=models.CharField(max_length=100,blank=True); imported_at=models.DateTimeField(null=True,blank=True); created_at=models.DateTimeField(auto_now_add=True); updated_at=models.DateTimeField(auto_now=True)
 class Meta: ordering=['first_name','last_name']; indexes=[models.Index(fields=['account_status']),models.Index(fields=['church_branch'])]
 def save(self,*args,**kwargs):
  if not self.member_number:
   last=Member.objects.order_by('-id').values_list('id',flat=True).first() or 0
   self.member_number=f'HSM-{last+1:06d}'
  super().save(*args,**kwargs)
 def __str__(self): return f'{self.member_number} Â· {self.first_name} {self.last_name}'

class AccountInvitation(models.Model):
 class Channel(models.TextChoices): SMS='SMS','SMS'; EMAIL='EMAIL','Email'
 class Status(models.TextChoices): PENDING='PENDING','Pending'; SENT='SENT','Sent'; FAILED='FAILED','Failed'; ACCEPTED='ACCEPTED','Accepted'; EXPIRED='EXPIRED','Expired'
 member=models.ForeignKey(Member,on_delete=models.CASCADE,related_name='account_invitations'); token_hash=models.CharField(max_length=64,unique=True)
 channel=models.CharField(max_length=10,choices=Channel.choices,default=Channel.SMS); destination=models.CharField(max_length=254); status=models.CharField(max_length=12,choices=Status.choices,default=Status.PENDING)
 sent_by=models.ForeignKey(settings.AUTH_USER_MODEL,null=True,blank=True,on_delete=models.SET_NULL,related_name='account_invitations_sent'); created_at=models.DateTimeField(auto_now_add=True); sent_at=models.DateTimeField(null=True,blank=True); expires_at=models.DateTimeField(); accepted_at=models.DateTimeField(null=True,blank=True); error=models.TextField(blank=True)
 class Meta: ordering=['-created_at']
 @staticmethod
 def hash_token(token): return hashlib.sha256(token.encode()).hexdigest()
 def is_valid(self): return self.status==self.Status.SENT and self.expires_at>timezone.now() and not self.accepted_at

