"""
Dynamic RBAC (role-based access control) for the Megawatt ERP.

Catalog (shared across the whole installation, seeded via the
``seed_permissions`` management command):

    Module   -> a navigable ERP section (maps 1:1 to a frontend CASL subject)
    Feature  -> a fine-grained unit inside a module (e.g. purchase orders)
    Permission -> a Feature x Action atom (e.g. ``procurement.purchase_order.add``)

Tenant data (per CompanyProfile):

    Role            -> a named bundle of Permissions, owned by a company
    RolePermission  -> through table linking a Role to the Permissions it grants
    StaffRole       -> assigns a Role to a StaffProfile (optionally branch-scoped)

A staff member's effective permissions are the union of the permissions of all
roles assigned to them. System administrators / super admins implicitly hold
every permission (see ``access_control.permissions``).
"""
from django.db import models
from django.utils import timezone

from system_administration.models import CompanyProfile, CompanyBranch
from human_resource.models import StaffProfile


# Canonical action verbs. Roles bundle Feature x Action permissions; the
# frontend CASL layer maps these onto its own action vocabulary.
ACTION_VIEW = 'view'
ACTION_ADD = 'add'
ACTION_EDIT = 'edit'
ACTION_DELETE = 'delete'
ACTION_APPROVE = 'approve'

ACTION_CHOICES = [
    (ACTION_VIEW, 'View'),
    (ACTION_ADD, 'Add'),
    (ACTION_EDIT, 'Edit'),
    (ACTION_DELETE, 'Delete'),
    (ACTION_APPROVE, 'Approve'),
]


class Module(models.Model):
    """Top-level navigable ERP section. Shared catalog (not per-company)."""
    code = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=255, blank=True, default='')
    # The CASL subject the frontend uses to gate routes/nav for this module,
    # e.g. 'Procurement', 'Warehouse', 'Finance'.
    casl_subject = models.CharField(max_length=50, blank=True, default='')
    icon = models.CharField(max_length=100, blank=True, default='')
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('order', 'name')
        verbose_name = 'Module'
        verbose_name_plural = 'Modules'

    def __str__(self):
        return self.name


class Feature(models.Model):
    """A fine-grained unit inside a Module. Shared catalog."""
    module = models.ForeignKey(
        Module, on_delete=models.CASCADE, related_name='features')
    code = models.CharField(max_length=80)
    name = models.CharField(max_length=120)
    description = models.CharField(max_length=255, blank=True, default='')
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('module__order', 'order', 'name')
        unique_together = (('module', 'code'),)
        verbose_name = 'Feature'
        verbose_name_plural = 'Features'

    def __str__(self):
        return f'{self.module.code}.{self.code}'


class Permission(models.Model):
    """A Feature x Action atom. Shared catalog. The ``codename`` is the stable
    identifier used everywhere in code (e.g. ``procurement.purchase_order.add``).
    """
    feature = models.ForeignKey(
        Feature, on_delete=models.CASCADE, related_name='permissions')
    action = models.CharField(max_length=20, choices=ACTION_CHOICES)
    codename = models.CharField(max_length=180, unique=True, db_index=True)
    name = models.CharField(max_length=180, blank=True, default='')
    is_active = models.BooleanField(default=True)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('feature__module__order', 'feature__order', 'action')
        unique_together = (('feature', 'action'),)
        verbose_name = 'Permission'
        verbose_name_plural = 'Permissions'

    def __str__(self):
        return self.codename

    def save(self, *args, **kwargs):
        if not self.codename and self.feature_id:
            self.codename = f'{self.feature.module.code}.{self.feature.code}.{self.action}'
        if not self.name:
            self.name = self.codename
        super().save(*args, **kwargs)


class Role(models.Model):
    """A named bundle of permissions, owned by a CompanyProfile (per-company)."""
    company_profile = models.ForeignKey(
        CompanyProfile, null=True, on_delete=models.CASCADE,
        related_name='roles')
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=255, blank=True, default='')
    permissions = models.ManyToManyField(
        Permission, through='RolePermission', related_name='roles', blank=True)
    # System roles are created by back-fill/seed and cannot be deleted via the
    # API; they can still have their permission set edited.
    is_system = models.BooleanField(default=False)
    recycle_bin = models.BooleanField(default=False)
    created_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL,
        related_name='roles_created')
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL,
        related_name='roles_updated')
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('name',)
        unique_together = (('company_profile', 'name'),)
        verbose_name = 'Role'
        verbose_name_plural = 'Roles'

    def __str__(self):
        return self.name


class RolePermission(models.Model):
    """Through table: a Role grants a Permission."""
    role = models.ForeignKey(
        Role, on_delete=models.CASCADE, related_name='role_permissions')
    permission = models.ForeignKey(
        Permission, on_delete=models.CASCADE, related_name='role_permissions')
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = (('role', 'permission'),)
        verbose_name = 'Role Permission'
        verbose_name_plural = 'Role Permissions'

    def __str__(self):
        return f'{self.role.name} -> {self.permission.codename}'


class StaffRole(models.Model):
    """Assigns a Role to a StaffProfile. A staff member may hold many roles;
    their effective permissions are the union across all active assignments.

    Optional branch scoping mirrors the legacy main-branch checks: when
    ``main_branch_only`` is set the role's permissions apply only while the
    staff member's branch is the company main branch.
    """
    staff_profile = models.ForeignKey(
        StaffProfile, on_delete=models.CASCADE, related_name='staff_roles')
    role = models.ForeignKey(
        Role, on_delete=models.CASCADE, related_name='staff_assignments')
    branch = models.ForeignKey(
        CompanyBranch, null=True, blank=True, on_delete=models.SET_NULL,
        related_name='staff_role_assignments')
    main_branch_only = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    assigned_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL,
        related_name='staff_roles_assigned')
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (('staff_profile', 'role'),)
        verbose_name = 'Staff Role'
        verbose_name_plural = 'Staff Roles'

    def __str__(self):
        return f'{self.staff_profile} : {self.role.name}'
