from datetime import timedelta

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone

from core.permissions import (
    Capability,
    Role,
    all_capabilities,
    capability_label,
    normalize_role,
    role_has_capability,
    role_matches,
)


def _capability_matches(granted: str, required: str) -> bool:
    if granted == "*" or granted == required:
        return True
    if granted.endswith(".*"):
        return required.startswith(granted[:-1])
    return False


class User(AbstractUser):
    class Role(models.TextChoices):
        SUPER_ADMIN = Role.SUPER_ADMIN, 'Super Admin'
        STORE_MANAGER = Role.STORE_MANAGER, 'Store Manager'
        STOREKEEPER = Role.STOREKEEPER, 'Storekeeper'
        ACCOUNTANT = Role.ACCOUNTANT, 'Accountant'
        SALES_OFFICER = Role.SALES_OFFICER, 'Sales Officer'
        AUDITOR = Role.AUDITOR, 'Auditor'

        # Legacy values retained for backward compatibility
        OWNER = Role.OWNER, 'Owner (Legacy)'
        MANAGER = Role.MANAGER, 'Manager (Legacy)'
        CASHIER = Role.CASHIER, 'Cashier (Legacy)'

    role = models.CharField(max_length=20, choices=Role.choices, default=Role.CASHIER)
    contact = models.CharField(max_length=20, blank=True)
    is_active = models.BooleanField(default=True)
    must_change_password = models.BooleanField(default=False)
    failed_login_attempts = models.PositiveSmallIntegerField(default=0)
    locked_until = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f"{self.username} ({self.get_role_display()})"

    @property
    def is_owner(self):
        return self.effective_role == Role.SUPER_ADMIN

    @property
    def is_manager(self):
        return self.effective_role == Role.STORE_MANAGER

    @property
    def is_cashier(self):
        return self.effective_role == Role.SALES_OFFICER

    @property
    def is_storekeeper(self):
        return self.effective_role == Role.STOREKEEPER

    @property
    def is_accountant(self):
        return self.effective_role == Role.ACCOUNTANT

    @property
    def is_auditor(self):
        return self.effective_role == Role.AUDITOR

    @property
    def effective_role(self):
        return normalize_role(self.role)

    def has_role(self, *roles):
        return role_matches(self.role, roles)

    def has_capability(self, capability):
        if role_has_capability(self.role, capability):
            return True
        granted = GroupCapability.objects.filter(group__user=self).values_list(
            "capability", flat=True
        ).distinct()
        return any(_capability_matches(code, capability) for code in granted)

    def has_any_capability(self, *capabilities):
        return any(self.has_capability(capability) for capability in capabilities)

    @property
    def can_manage_users(self):
        return self.has_capability(Capability.USERS_MANAGE)

    @property
    def can_view_audit_logs(self):
        return self.has_capability(Capability.AUDIT_VIEW)

    @property
    def can_access_inventory(self):
        return self.has_capability(Capability.INVENTORY_READ)

    @property
    def can_access_sales(self):
        return self.has_any_capability(
            Capability.SALES_READ_ALL,
            Capability.SALES_READ_OWN,
            Capability.SALES_CREATE,
        )

    @property
    def can_access_finance(self):
        return self.has_capability(Capability.FINANCE_READ)

    @property
    def can_view_reports(self):
        return self.has_capability(Capability.REPORTS_VIEW)

    @property
    def can_create_sales(self):
        return self.has_capability(Capability.SALES_CREATE)

    @property
    def can_access_assets(self):
        return self.has_capability(Capability.ASSET_READ)

    @property
    def can_view_inventory_reports(self):
        return self.has_capability(Capability.REPORTS_VIEW)

    @property
    def phone(self):
        return self.contact

    @phone.setter
    def phone(self, value):
        self.contact = value or ""

    @property
    def is_locked(self):
        return bool(self.locked_until and self.locked_until > timezone.now())

    def register_failed_login(self, max_attempts=5, lockout_minutes=15):
        self.failed_login_attempts = (self.failed_login_attempts or 0) + 1
        if self.failed_login_attempts >= max_attempts:
            self.locked_until = timezone.now() + timedelta(minutes=lockout_minutes)
            self.failed_login_attempts = 0
        self.save(update_fields=["failed_login_attempts", "locked_until"])

    def reset_failed_login_state(self):
        if self.failed_login_attempts or self.locked_until:
            self.failed_login_attempts = 0
            self.locked_until = None
            self.save(update_fields=["failed_login_attempts", "locked_until"])

    @classmethod
    def assignable_role_choices(cls):
        return [
            (cls.Role.SUPER_ADMIN, "Super Admin"),
            (cls.Role.STORE_MANAGER, "Store Manager"),
            (cls.Role.STOREKEEPER, "Storekeeper"),
            (cls.Role.ACCOUNTANT, "Accountant"),
            (cls.Role.SALES_OFFICER, "Sales Officer"),
            (cls.Role.AUDITOR, "Auditor"),
        ]


class Employee(models.Model):
    code = models.CharField(max_length=40, unique=True)
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150, blank=True)
    email = models.EmailField(blank=True)
    phone = models.CharField(max_length=20, blank=True)
    role = models.CharField(
        max_length=20,
        choices=User.assignable_role_choices(),
        default=Role.SALES_OFFICER,
    )
    is_active = models.BooleanField(default=True)
    user = models.OneToOneField(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="employee_record",
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("first_name", "last_name", "code")

    def __str__(self):
        return f"{self.code} - {self.full_name}".strip()

    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}".strip()


class GroupCapability(models.Model):
    group = models.ForeignKey(
        "auth.Group", on_delete=models.CASCADE, related_name="capability_bindings"
    )
    capability = models.CharField(
        max_length=80,
        choices=[(code, capability_label(code)) for code in all_capabilities()],
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ("group", "capability")
        ordering = ("group__name", "capability")

    def __str__(self):
        return f"{self.group.name} -> {self.capability}"
