"""
Seed the shared permission catalog and back-fill per-company roles + staff
assignments from the legacy department / flag model, so that switching a view
to RBAC never removes access an existing user already had.

Idempotent: uses get_or_create throughout. The catalog seeding mirrors the
``seed_permissions`` management command (kept in sync via catalog.py).
"""
from django.db import migrations

from access_control.catalog import MODULES, DEPARTMENT_TO_MODULE


def _seed_catalog(apps):
    Module = apps.get_model('access_control', 'Module')
    Feature = apps.get_model('access_control', 'Feature')
    Permission = apps.get_model('access_control', 'Permission')

    # codename -> Permission, grouped by module code for role building.
    perms_by_module = {}
    for m_order, m in enumerate(MODULES):
        module, _ = Module.objects.get_or_create(
            code=m['code'],
            defaults={'name': m['name'], 'casl_subject': m.get('casl_subject', ''),
                      'icon': m.get('icon', ''), 'order': m_order, 'is_active': True},
        )
        # keep metadata fresh
        module.name = m['name']
        module.casl_subject = m.get('casl_subject', '')
        module.icon = m.get('icon', '')
        module.order = m_order
        module.is_active = True
        module.save()

        perms_by_module.setdefault(m['code'], {'all': [], 'view': []})
        for f_order, f in enumerate(m['features']):
            feature, _ = Feature.objects.get_or_create(
                module=module, code=f['code'],
                defaults={'name': f['name'], 'order': f_order, 'is_active': True},
            )
            for action in f['actions']:
                codename = f"{m['code']}.{f['code']}.{action}"
                perm, _ = Permission.objects.get_or_create(
                    feature=feature, action=action,
                    defaults={'codename': codename,
                              'name': f"{f['name']} - {action.title()}",
                              'is_active': True},
                )
                perms_by_module[m['code']]['all'].append(perm)
                if action == 'view':
                    perms_by_module[m['code']]['view'].append(perm)
    return perms_by_module


def _all_perms(perms_by_module):
    result = []
    for data in perms_by_module.values():
        result.extend(data['all'])
    return result


def forwards(apps, schema_editor):
    CompanyProfile = apps.get_model('system_administration', 'CompanyProfile')
    StaffProfile = apps.get_model('human_resource', 'StaffProfile')
    Role = apps.get_model('access_control', 'Role')
    RolePermission = apps.get_model('access_control', 'RolePermission')
    StaffRole = apps.get_model('access_control', 'StaffRole')

    perms_by_module = _seed_catalog(apps)
    module_name = {m['code']: m['name'] for m in MODULES}

    def grant(role, permissions):
        for perm in permissions:
            RolePermission.objects.get_or_create(role=role, permission=perm)

    def ensure_role(company, name, description=''):
        role, _ = Role.objects.get_or_create(
            company_profile=company, name=name,
            defaults={'description': description, 'is_system': True},
        )
        return role

    # Build the role catalogue once per company.
    for company in CompanyProfile.objects.all():
        admin_role = ensure_role(company, 'System Administrator',
                                 'Full, unrestricted access to every module.')
        grant(admin_role, _all_perms(perms_by_module))

        general_role = ensure_role(company, 'General Access',
                                   'Baseline access granted to all active staff.')
        grant(general_role, perms_by_module.get('reports', {}).get('view', []))

        for mod_code, data in perms_by_module.items():
            if mod_code in ('system_administration', 'reports'):
                continue
            mgr = ensure_role(company, f'{module_name[mod_code]} Manager',
                              f'Full access to the {module_name[mod_code]} module.')
            grant(mgr, data['all'])
            officer = ensure_role(company, f'{module_name[mod_code]} Officer',
                                  f'Read-only access to the {module_name[mod_code]} module.')
            grant(officer, data['view'])

    def role_for(company, name):
        return Role.objects.filter(company_profile=company, name=name).first()

    def assign(staff, role):
        if role is None:
            return
        StaffRole.objects.get_or_create(
            staff_profile=staff, role=role, defaults={'is_active': True})

    # Back-fill staff -> roles from the legacy department + flags.
    staff_qs = StaffProfile.objects.select_related(
        'company_branch__company_profile', 'company_department').all()
    for staff in staff_qs:
        branch = staff.company_branch
        company = branch.company_profile if branch is not None else None
        if company is None:
            continue

        # Everyone active gets baseline access.
        if getattr(staff, 'is_profile_active', True) and not getattr(staff, 'recycle_bin', False):
            assign(staff, role_for(company, 'General Access'))

        dept = staff.company_department
        dept_name = dept.department_name if dept is not None else None

        if getattr(staff, 'is_super_admin', False) or dept_name == 'system_and_administration':
            assign(staff, role_for(company, 'System Administrator'))
            continue

        mod_code = DEPARTMENT_TO_MODULE.get(dept_name or '')
        if not mod_code or mod_code in ('system_administration',):
            continue
        mod_label = module_name[mod_code]
        if getattr(staff, 'is_head_of_department', False) and getattr(staff, 'has_read_write_priviledges', False):
            assign(staff, role_for(company, f'{mod_label} Manager'))
        else:
            assign(staff, role_for(company, f'{mod_label} Officer'))


def backwards(apps, schema_editor):
    # Remove only the system roles + their assignments created here.
    Role = apps.get_model('access_control', 'Role')
    StaffRole = apps.get_model('access_control', 'StaffRole')
    system_roles = Role.objects.filter(is_system=True)
    StaffRole.objects.filter(role__in=system_roles).delete()
    system_roles.delete()


class Migration(migrations.Migration):

    dependencies = [
        ('access_control', '0001_initial'),
        ('system_administration', '0005_companysmsettings'),
        ('human_resource', '0011_staffdeductionscheme_overrides'),
    ]

    operations = [
        migrations.RunPython(forwards, backwards),
    ]
