from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

class Role(models.Model):
    name = models.CharField(max_length=50, unique=True)
    
    class Meta:
        db_table = 'roles'
        
    def __str__(self):
        return self.name

class User(models.Model):
    STATUS_CHOICES = [
        ('ACTIVE', 'Active'),
        ('INACTIVE', 'Inactive'),
        ('SUSPENDED', 'Suspended')
    ]
    
    role = models.ForeignKey(Role, on_delete=models.CASCADE)
    full_name = models.CharField(max_length=150)
    email = models.CharField(max_length=150, unique=True)
    phone = models.CharField(max_length=30, unique=True, null=True)
    password_hash = models.CharField(max_length=255)
    email_verified = models.BooleanField(default=False)
    phone_verified = models.BooleanField(default=False)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE')
    failed_login_attempts = models.IntegerField(default=0)
    account_locked_until = models.DateTimeField(null=True, blank=True)
    password_changed_at = models.DateTimeField(null=True, blank=True)
    last_login_at = models.DateTimeField(null=True, blank=True)
    avatar_color = models.CharField(max_length=7, null=True, blank=True)
    avatar = models.FileField(upload_to='avatars/', null=True, blank=True)
    token_version = models.IntegerField(default=0)  # bump to instantly invalidate every JWT (logout-all)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'users'
        
    def __str__(self):
        return f"{self.full_name} ({self.email})"

class City(models.Model):
    PROVINCE_CHOICES = [
        ('KOSHI', 'Koshi Province'),
        ('MADHESH', 'Madhesh Province'),
        ('BAGMATI', 'Bagmati Province'),
        ('GANDAKI', 'Gandaki Province'),
        ('LUMBINI', 'Lumbini Province'),
        ('KARNALI', 'Karnali Province'),
        ('SUDURPASHCHIM', 'Sudurpashchim Province'),
        ('OTHER', 'Other'),
    ]
    city_name = models.CharField(max_length=100, unique=True)
    province = models.CharField(max_length=20, choices=PROVINCE_CHOICES, default='OTHER')
    
    class Meta:
        db_table = 'cities'
    
    def __str__(self):
        return self.city_name
    

class Operator(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    company_name = models.CharField(max_length=200)
    registration_number = models.CharField(max_length=100, null=True)
    document_encrypted = models.TextField(null=True)
    verified = models.BooleanField(default=False)
    
    class Meta:
        db_table = 'operators'
    
    def __str__(self):
        return f"{self.company_name} ({'Verified' if self.verified else 'Unverified'})"

class Bus(models.Model):
    BUS_TYPE_CHOICES = [
        ('NORMAL', 'Normal'),
        ('AC', 'AC'),
        ('DELUXE', 'Deluxe'),
        ('VIP', 'VIP')
    ]
    STATUS_CHOICES = [
        ('ACTIVE', 'Active'),
        ('INACTIVE', 'Inactive')
    ]
    
    operator = models.ForeignKey(Operator, on_delete=models.CASCADE)
    bus_name = models.CharField(max_length=150, null=True)
    registration_no = models.CharField(max_length=100, unique=True, null=True)
    bus_type = models.CharField(max_length=20, choices=BUS_TYPE_CHOICES, null=True)
    total_seats = models.IntegerField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE')
    
    APPROVAL_CHOICES = [
        ('PENDING', 'Pending Review'),
        ('APPROVED', 'Approved'),
        ('REJECTED', 'Rejected'),
    ]
    approval_status = models.CharField(max_length=20, choices=APPROVAL_CHOICES, default='PENDING')
    review_note = models.CharField(max_length=500, null=True, blank=True)
    reviewed_at = models.DateTimeField(null=True, blank=True)
    reviewed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_buses')
    created_at = models.DateTimeField(auto_now_add=True, null=True)

    # --- Seat-map / layout (FR-20) ---
    deck_count = models.IntegerField(default=1)
    seat_rows = models.IntegerField(null=True, blank=True)
    seat_cols = models.IntegerField(null=True, blank=True)
    has_seat_map = models.BooleanField(default=False)  # True once BusSeat rows exist
    amenities = models.CharField(max_length=500, blank=True, default='')  # comma-separated codes

    class Meta:
        db_table = 'buses'
    
    def __str__(self):
        return f"{self.bus_name or self.registration_no or 'Bus'} ({self.operator.company_name})"

    def amenity_list(self):
        return [a.strip() for a in (self.amenities or '').split(',') if a.strip()]

    def is_fully_verified(self):
        """True once all 4 required per-bus documents are admin-verified.
        A bus only becomes bookable once this AND approval_status='APPROVED'
        are both true (see is_bookable())."""
        verified_count = self.documents.filter(status='VERIFIED').values('doc_type').distinct().count()
        return verified_count >= len(BusDocument.REQUIRED_DOC_TYPES)

    def is_bookable(self):
        """The real, combined gate passengers/scheduling should check."""
        return self.approval_status == 'APPROVED' and self.status == 'ACTIVE' and self.is_fully_verified()


class BusSeat(models.Model):
    """One physical seat on a bus. Generated when an operator configures a seat map."""
    SEAT_TYPE_CHOICES = [
        ('STANDARD', 'Standard'),
        ('VIP', 'VIP'),
        ('SLEEPER', 'Sleeper'),
        ('FEMALE_ONLY', 'Female Only'),
        ('BLOCKED', 'Blocked (not for sale)'),
    ]
    bus = models.ForeignKey(Bus, on_delete=models.CASCADE, related_name='seats')
    deck = models.IntegerField(default=1)
    row_number = models.IntegerField()
    col_number = models.IntegerField()
    seat_number = models.CharField(max_length=10)  # human label e.g. "A1", "U3"
    seat_type = models.CharField(max_length=20, choices=SEAT_TYPE_CHOICES, default='STANDARD')
    is_active = models.BooleanField(default=True)  # False = out of service

    class Meta:
        db_table = 'bus_seats'
        unique_together = ('bus', 'seat_number')
        ordering = ['deck', 'row_number', 'col_number']

    def __str__(self):
        return f"{self.bus} seat {self.seat_number}"


class Route(models.Model):
    source_city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='source_routes')
    destination_city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='destination_routes')
    distance_km = models.DecimalField(max_digits=10, decimal_places=2, null=True)
    estimated_duration_minutes = models.IntegerField(null=True)
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_routes')
    
    class Meta:
        db_table = 'routes'
    
    def __str__(self):
        return f"{self.source_city.city_name} → {self.destination_city.city_name}"

class Schedule(models.Model):
    STATUS_CHOICES = [
        ('ACTIVE', 'Active'),
        ('CANCELLED', 'Cancelled'),
        ('COMPLETED', 'Completed'),
        ('DELAYED', 'Delayed'),
    ]
    
    bus = models.ForeignKey(Bus, on_delete=models.CASCADE)
    route = models.ForeignKey(Route, on_delete=models.CASCADE)
    departure_time = models.DateTimeField()
    arrival_time = models.DateTimeField()
    boarding_point_id = models.BigIntegerField(null=True, blank=True)
    dropping_point_id = models.BigIntegerField(null=True, blank=True)
    fare = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE')

    # --- Delay tracking (BR-11) ---
    original_departure_time = models.DateTimeField(null=True, blank=True)
    delay_note = models.CharField(max_length=255, null=True, blank=True)
    
    class Meta:
        db_table = 'schedules'
    
    def __str__(self):
        return f"{self.route} - {self.departure_time.strftime('%Y-%m-%d %H:%M')} (Rs. {self.fare})"

class Booking(models.Model):
    STATUS_CHOICES = [
        ('PENDING', 'Pending'),
        ('PENDING_PAYMENT', 'Pending Payment'),
        ('CONFIRMED', 'Confirmed'),
        ('EXPIRED', 'Expired'),
        ('CANCELLED', 'Cancelled'),
        ('REFUNDED', 'Refunded'),
        ('CHECKED_IN', 'Checked In'),
        ('COMPLETED', 'Completed')
    ]
    CANCELLED_BY_CHOICES = [
        ('PASSENGER', 'Passenger'),
        ('OPERATOR', 'Operator'),
        ('ADMIN', 'Admin'),
        ('SYSTEM', 'System (expiry)'),
    ]
    
    booking_code = models.CharField(max_length=50, unique=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE)
    total_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True)
    booking_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
    booked_at = models.DateTimeField(auto_now_add=True)
    
    seats_count = models.IntegerField(default=1)
    commission_rate = models.DecimalField(max_digits=5, decimal_places=2, default=10.00)
    commission_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    operator_earning = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    cancel_reason = models.CharField(max_length=255, null=True, blank=True)
    cancelled_at = models.DateTimeField(null=True, blank=True)

    # --- New: payment window + cancellation-fee tier + who cancelled ---
    payment_deadline = models.DateTimeField(null=True, blank=True)
    cancellation_fee_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    cancelled_by = models.CharField(max_length=20, choices=CANCELLED_BY_CHOICES, null=True, blank=True)
    cancel_reason_code = models.CharField(max_length=50, null=True, blank=True)
    
    class Meta:
        db_table = 'bookings'
    
    def __str__(self):
        return f"{self.booking_code} - {self.user.full_name} ({self.booking_status})"


class SeatLock(models.Model):
    """Temporary hold on a seat while a passenger is completing checkout (BR-1)."""
    schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE, related_name='seat_locks')
    seat = models.ForeignKey(BusSeat, on_delete=models.CASCADE, related_name='locks')
    locked_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='seat_locks')
    expires_at = models.DateTimeField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'seat_locks'

    def __str__(self):
        return f"Lock {self.seat.seat_number} on schedule {self.schedule_id} until {self.expires_at:%H:%M:%S}"


class BookingSeat(models.Model):
    """One seat within a booking - the per-passenger, per-seat line item (FR-12)."""
    GENDER_CHOICES = [('M', 'Male'), ('F', 'Female'), ('O', 'Other')]

    booking = models.ForeignKey(Booking, on_delete=models.CASCADE, related_name='booking_seats')
    schedule = models.ForeignKey(Schedule, on_delete=models.CASCADE, related_name='booking_seats')
    seat = models.ForeignKey(BusSeat, on_delete=models.CASCADE, related_name='booking_seats')
    passenger_name = models.CharField(max_length=150, blank=True, default='')
    passenger_age = models.IntegerField(null=True, blank=True)
    passenger_gender = models.CharField(max_length=1, choices=GENDER_CHOICES, null=True, blank=True)
    boarding_point = models.CharField(max_length=150, blank=True, default='')
    dropping_point = models.CharField(max_length=150, blank=True, default='')
    checked_in = models.BooleanField(default=False)
    checked_in_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        db_table = 'booking_seats'

    def __str__(self):
        return f"{self.booking.booking_code} - seat {self.seat.seat_number}"


class Payment(models.Model):
    """A payment attempt for a booking (currently: eSewa ePay v2)."""
    GATEWAY_CHOICES = [('ESEWA', 'eSewa')]
    STATUS_CHOICES = [
        ('INITIATED', 'Initiated'),   # form built, not yet redirected/returned
        ('PENDING', 'Pending'),
        ('COMPLETE', 'Complete'),
        ('FAILED', 'Failed'),
        ('CANCELED', 'Canceled'),
        ('FULL_REFUND', 'Full Refund'),
        ('PARTIAL_REFUND', 'Partial Refund'),
        ('AMBIGUOUS', 'Ambiguous'),
        ('NOT_FOUND', 'Not Found'),
    ]

    booking = models.ForeignKey(Booking, on_delete=models.CASCADE, related_name='payments')
    gateway = models.CharField(max_length=20, choices=GATEWAY_CHOICES, default='ESEWA')
    transaction_uuid = models.CharField(max_length=100, unique=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='INITIATED')
    ref_id = models.CharField(max_length=100, null=True, blank=True)
    raw_response = models.TextField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'payments'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.transaction_uuid} ({self.status}) Rs.{self.amount}"


class PaymentWebhookLog(models.Model):
    """Append-only log of every payment callback/verify response received (§7.2.12)."""
    payment = models.ForeignKey(Payment, on_delete=models.SET_NULL, null=True, blank=True, related_name='webhook_logs')
    transaction_uuid = models.CharField(max_length=100, blank=True, default='')
    raw_payload = models.TextField()
    signature_valid = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'payment_webhook_logs'
        ordering = ['-created_at']

    def __str__(self):
        return f"Webhook {self.transaction_uuid} @ {self.created_at:%Y-%m-%d %H:%M:%S}"


class BusDocument(models.Model):
    """The 4 required documents for one specific bus (not the company).
    A company with 3 buses needs 3 separate, independent sets of these -
    each bus has its own bluebook, insurance, and assigned driver's license.
    Exactly one row per (bus, doc_type) - re-uploading replaces the file and
    resets status to PENDING rather than creating a new row."""
    DOC_TYPE_CHOICES = [
        ('BLUEBOOK_OWNERSHIP', 'Vehicle Bluebook — Ownership Page'),
        ('BLUEBOOK_TAX', 'Vehicle Bluebook — Tax Renewal Page'),
        ('DRIVING_LICENSE', "Driver's License"),
        ('INSURANCE', 'Vehicle Insurance'),
    ]
    REQUIRED_DOC_TYPES = [c[0] for c in DOC_TYPE_CHOICES]  # all 4 are required, no optional types

    STATUS_CHOICES = [
        ('PENDING', 'Pending Review'),
        ('VERIFIED', 'Verified'),
        ('REJECTED', 'Rejected'),
    ]

    bus = models.ForeignKey(Bus, on_delete=models.CASCADE, related_name='documents')
    doc_type = models.CharField(max_length=20, choices=DOC_TYPE_CHOICES)
    file = models.FileField(upload_to='bus_docs/')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
    review_note = models.CharField(max_length=500, null=True, blank=True)
    reviewed_at = models.DateTimeField(null=True, blank=True)
    reviewed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_bus_documents')
    uploaded_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'bus_documents'
        unique_together = ('bus', 'doc_type')
        ordering = ['doc_type']

    def can_be_replaced_by_operator(self):
        """DRIVING_LICENSE stays editable forever (drivers rotate); everything
        else locks once VERIFIED and can only be replaced if REJECTED."""
        if self.doc_type == 'DRIVING_LICENSE':
            return True
        return self.status in ('PENDING', 'REJECTED')

    def __str__(self):
        return f"{self.bus} - {self.doc_type} ({self.status})"


class OperatorDocument(models.Model):
    """Documents an operator submits during onboarding (FR-18)."""
    DOC_TYPE_CHOICES = [
        ('COMPANY_REGISTRATION', 'Company / Firm Registration'),
        ('PAN_VAT', 'PAN / VAT Registration'),
        ('BLUEBOOK_OWNERSHIP', 'Vehicle Bluebook — Ownership Page'),
        ('BLUEBOOK_TAX', 'Vehicle Bluebook — Tax Renewal Page'),
        ('ROUTE_PERMIT', 'Route Permit'),
        ('DRIVING_LICENSE', "Driver's License"),
        ('INSURANCE', 'Vehicle Insurance'),
        ('OTHER', 'Other'),
    ]
    STATUS_CHOICES = [
        ('PENDING', 'Pending Review'),
        ('VERIFIED', 'Verified'),
        ('REJECTED', 'Rejected'),
    ]

    operator = models.ForeignKey(Operator, on_delete=models.CASCADE, related_name='documents')
    doc_type = models.CharField(max_length=20, choices=DOC_TYPE_CHOICES, default='OTHER')
    file = models.FileField(upload_to='operator_docs/')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
    review_note = models.CharField(max_length=500, null=True, blank=True)
    reviewed_at = models.DateTimeField(null=True, blank=True)
    reviewed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_documents')
    uploaded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'operator_documents'
        ordering = ['-uploaded_at']

    def __str__(self):
        return f"{self.operator.company_name} - {self.doc_type} ({self.status})"


class Rating(models.Model):
    """Passenger rating/review of a completed trip (FR-17)."""
    booking = models.OneToOneField(Booking, on_delete=models.CASCADE, related_name='rating')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ratings_given')
    operator = models.ForeignKey(Operator, on_delete=models.CASCADE, related_name='ratings_received')
    stars = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
    review = models.TextField(blank=True, default='')
    issue_flag = models.BooleanField(default=False)  # reported issue -> admin queue, not published
    issue_note = models.TextField(blank=True, default='')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'ratings'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.stars}★ on {self.booking.booking_code}"


class DeviceTrust(models.Model):
    """Remembers a browser for 7 days so an operator skips OTP on repeat logins."""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='trusted_devices')
    device_id = models.CharField(max_length=100)
    expires_at = models.DateTimeField()
    created_at = models.DateTimeField(auto_now_add=True)
    last_used_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'device_trust'
        unique_together = ('user', 'device_id')

    def is_valid(self):
        from django.utils import timezone
        return self.expires_at > timezone.now()

    def __str__(self):
        return f"{self.user.email} trusted until {self.expires_at:%Y-%m-%d %H:%M}"


class LoginLog(models.Model):
    """Records every login attempt (success, failure, lockout, logout)."""
    STATUS_CHOICES = [
        ('SUCCESS', 'Success'),
        ('FAILED', 'Failed'),
        ('LOCKED', 'Locked'),
        ('LOGOUT', 'Logout'),
        ('OTP_SENT', 'OTP Sent'),
    ]

    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='login_logs')
    email_attempted = models.CharField(max_length=150)
    role_name = models.CharField(max_length=50, null=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES)
    ip_address = models.CharField(max_length=100, null=True, blank=True)
    user_agent = models.CharField(max_length=255, null=True, blank=True)
    message = models.CharField(max_length=255, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'login_logs'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.email_attempted} - {self.status} @ {self.created_at:%Y-%m-%d %H:%M:%S}"


class LoginOTP(models.Model):
    """One-time codes emailed to operators for 2-step login."""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='login_otps')
    code = models.CharField(max_length=6)
    attempts = models.IntegerField(default=0)
    used = models.BooleanField(default=False)
    expires_at = models.DateTimeField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'login_otps'
        ordering = ['-created_at']

    def __str__(self):
        return f"OTP for {self.user.email} ({'used' if self.used else 'active'})"
    
class UserRoleAccess(models.Model):
    """Extra roles a user may login as, besides their primary users.role_id."""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='role_accesses')
    role = models.ForeignKey(Role, on_delete=models.CASCADE)
    granted_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'user_role_access'
        unique_together = ('user', 'role')

    def __str__(self):
        return f"{self.user.email} can also login as {self.role.name}"

class ActivityLog(models.Model):
    """Passenger and Operator activity audit trail — separate from AdminAudit
    (which is specifically admin moderation actions). Kept as one table with
    a `role` column so the admin UI can filter into two separate views."""
    ROLE_CHOICES = [('PASSENGER', 'Passenger'), ('OPERATOR', 'Operator')]

    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='activity_logs')
    role = models.CharField(max_length=20, choices=ROLE_CHOICES)
    action = models.CharField(max_length=50)   # e.g. BOOKING_CREATED, BUS_CREATED, TRIP_CREATED
    detail = models.CharField(max_length=500)  # human-readable summary
    amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)  # revenue/refund events
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'activity_logs'
        ordering = ['-created_at']

    def __str__(self):
        return f"[{self.role}] {self.action} - {self.detail[:40]}"


class AdminAudit(models.Model):
    """Audit trail for admin actions (user suspend, operator suspend, manual refund)."""
    admin = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='admin_actions')
    action = models.CharField(max_length=50)          # e.g. USER_SUSPEND, OPERATOR_SUSPEND, MANUAL_REFUND
    target = models.CharField(max_length=150)         # who/what it affected
    reason_code = models.CharField(max_length=50, null=True, blank=True)
    note = models.CharField(max_length=500, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'admin_audit'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.action} → {self.target} @ {self.created_at:%Y-%m-%d %H:%M}"