import { PrismaClient } from '@prisma/client';
import { PERMISSION_SEEDS } from './permissions.seed';
import { ROLE_SEEDS } from './roles.seed';
import { WORKFLOW_ROLE_SEEDS } from './workflow-roles.seed';
import { DATA_SCOPE_SEEDS, ROLE_DATA_SCOPE_SEEDS } from './data-scopes.seed';

const prisma = new PrismaClient();

/**
 * Seed IAM data (permissions, roles, workflow roles)
 */
export async function seedIam() {
  console.log('🌱 Seeding IAM data...');

  // 1. Create permissions
  console.log('Creating permissions...');
  for (const permissionSeed of PERMISSION_SEEDS) {
    await prisma.permission.upsert({
      where: {
        resource_action: {
          resource: permissionSeed.resource,
          action: permissionSeed.action,
        },
      },
      update: {
        description: permissionSeed.description,
        module: permissionSeed.module,
      },
      create: {
        resource: permissionSeed.resource,
        action: permissionSeed.action,
        description: permissionSeed.description,
        module: permissionSeed.module,
        isBuiltIn: true,
      },
    });
  }
  console.log(`✅ Created ${PERMISSION_SEEDS.length} permissions`);

  // 2. Create roles
  console.log('Creating roles...');
  await prisma.role.deleteMany({
    where: { code: 'Manager' },
  });
  for (const roleSeed of ROLE_SEEDS) {
    const role = await prisma.role.upsert({
      where: { code: roleSeed.code },
      update: {
        name: roleSeed.name,
        description: roleSeed.description,
      },
      create: {
        name: roleSeed.name,
        code: roleSeed.code,
        description: roleSeed.description,
        isBuiltIn: roleSeed.isBuiltIn,
        enabled: true,
      },
    });

    // 3. Assign permissions to role
    // First delete existing permission associations
    await prisma.rolePermission.deleteMany({
      where: { roleId: role.id },
    });

    // Process permission list
    for (const permissionCode of roleSeed.permissions) {
      if (permissionCode.includes('*')) {
        // Wildcard permission, e.g. user:* means all user permissions
        const [resource, action] = permissionCode.split(':');

        const permissions = await prisma.permission.findMany({
          where: {
            resource: resource,
            ...(action !== '*' ? { action } : {}),
          },
        });

        for (const permission of permissions) {
          await prisma.rolePermission.upsert({
            where: {
              roleId_permissionId: {
                roleId: role.id,
                permissionId: permission.id,
              },
            },
            update: {},
            create: {
              roleId: role.id,
              permissionId: permission.id,
            },
          });
        }
      } else {
        // Specific permission, e.g. user:read or approval:definition:create
        const [resource, ...actionParts] = permissionCode.split(':');
        const action = actionParts.join(':');
        const permission = await prisma.permission.findUnique({
          where: {
            resource_action: { resource, action },
          },
        });

        if (permission) {
          await prisma.rolePermission.upsert({
            where: {
              roleId_permissionId: {
                roleId: role.id,
                permissionId: permission.id,
              },
            },
            update: {},
            create: {
              roleId: role.id,
              permissionId: permission.id,
            },
          });
        }
      }
    }
  }
  console.log(`✅ Created ${ROLE_SEEDS.length} roles`);

  // 4. Create workflow roles
  console.log('Creating workflow roles...');
  for (const workflowRoleSeed of WORKFLOW_ROLE_SEEDS) {
    await prisma.workflowRole.upsert({
      where: { code: workflowRoleSeed.code },
      update: {
        name: workflowRoleSeed.name,
        description: workflowRoleSeed.description,
        ruleType: workflowRoleSeed.ruleType,
        ruleConfig: workflowRoleSeed.ruleConfig,
      },
      create: {
        name: workflowRoleSeed.name,
        code: workflowRoleSeed.code,
        description: workflowRoleSeed.description,
        ruleType: workflowRoleSeed.ruleType,
        ruleConfig: workflowRoleSeed.ruleConfig,
      },
    });
  }
  console.log(`✅ Created ${WORKFLOW_ROLE_SEEDS.length} workflow roles`);

  // 5. Create data scopes
  console.log('Creating data scopes...');
  for (const dsSeed of DATA_SCOPE_SEEDS) {
    await prisma.dataScope.upsert({
      where: { code: dsSeed.code },
      update: { name: dsSeed.name, scopeType: dsSeed.scopeType },
      create: {
        code: dsSeed.code,
        name: dsSeed.name,
        scopeType: dsSeed.scopeType,
        isBuiltIn: true,
      },
    });
  }
  console.log(`✅ Created ${DATA_SCOPE_SEEDS.length} data scopes`);

  // 6. Assign data scopes to roles（声明式：seed 是内置角色绑定的事实源，
  //    会清理掉 DB 里多余的、不在 seed 里的绑定，避免改了 seed 但旧绑定残留导致
  //    决议时取最宽松而把范围意外打开）
  console.log('Assigning data scopes to roles (declarative — stale entries removed)...');

  // 按 role 聚合 desired bindings
  const desiredByRoleCode = new Map<string, Array<{ dataScopeId: string; resource: string }>>();
  const roleCodes = new Set(ROLE_DATA_SCOPE_SEEDS.map((s) => s.roleCode));
  for (const code of roleCodes) desiredByRoleCode.set(code, []);

  for (const rdsSeed of ROLE_DATA_SCOPE_SEEDS) {
    const role = await prisma.role.findUnique({ where: { code: rdsSeed.roleCode } });
    const dataScope = await prisma.dataScope.findUnique({ where: { code: rdsSeed.dataScopeCode } });
    if (!role || !dataScope) {
      console.warn(`⚠️ Skipping: role=${rdsSeed.roleCode} or dataScope=${rdsSeed.dataScopeCode} not found`);
      continue;
    }
    desiredByRoleCode.get(rdsSeed.roleCode)!.push({
      dataScopeId: dataScope.id,
      resource: rdsSeed.resource,
    });
  }

  let assignedCount = 0;
  let removedCount = 0;
  for (const [roleCode, desired] of desiredByRoleCode) {
    const role = await prisma.role.findUnique({ where: { code: roleCode } });
    if (!role) continue;

    const existing = await prisma.roleDataScope.findMany({ where: { roleId: role.id } });
    const desiredKeys = new Set(desired.map((d) => `${d.dataScopeId}|${d.resource}`));
    const stale = existing.filter((e) => !desiredKeys.has(`${e.dataScopeId}|${e.resource}`));

    for (const s of stale) {
      await prisma.roleDataScope.delete({ where: { id: s.id } });
      removedCount++;
    }

    for (const d of desired) {
      await prisma.roleDataScope.upsert({
        where: {
          roleId_dataScopeId_resource: {
            roleId: role.id,
            dataScopeId: d.dataScopeId,
            resource: d.resource,
          },
        },
        update: {},
        create: {
          roleId: role.id,
          dataScopeId: d.dataScopeId,
          resource: d.resource,
        },
      });
      assignedCount++;
    }
  }
  console.log(`✅ Assigned ${assignedCount} role-dataScope bindings` + (removedCount > 0 ? ` (removed ${removedCount} stale)` : ''));

  console.log('✅ IAM data seeding completed!');
}

// If running directly
if (require.main === module) {
  seedIam()
    .then(() => {
      console.log('✅ IAM seed completed successfully');
      process.exit(0);
    })
    .catch((e) => {
      console.error('❌ Error seeding data:', e);
      process.exit(1);
    })
    .finally(async () => {
      await prisma.$disconnect();
    });
}
