// ============================================================
// RBAC MODULE — Klavo
// Perfis customizáveis + permissões granulares por módulo/ação
// ============================================================
import { Module, Injectable, NotFoundException, ConflictException,
  ForbiddenException, CanActivate, ExecutionContext, SetMetadata,
  createParamDecorator } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Repository } from 'typeorm';
import {
  Entity, PrimaryGeneratedColumn, Column, CreateDateColumn,
  UpdateDateColumn, ManyToMany, JoinTable, ManyToOne, JoinColumn,
} from 'typeorm';
import {
  Controller, Get, Post, Put, Delete, Patch,
  Body, Param, UseGuards, Request, HttpCode, HttpStatus,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import {
  IsString, IsOptional, IsArray, IsUUID, MinLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { UserTenantMembership } from '../auth/auth.module';

// ============================================================
// MÓDULOS DISPONÍVEIS NO SISTEMA
// ============================================================
export const KLAVO_MODULES = [
  'clients', 'products', 'proposals', 'contracts',
  'charges', 'invoices', 'signatures', 'saas', 'rbac', 'settings',
] as const;

export const KLAVO_ACTIONS = [
  'create', 'read', 'update', 'delete', 'approve', 'export', 'admin',
] as const;

export type KlavoModule = typeof KLAVO_MODULES[number];
export type KlavoAction = typeof KLAVO_ACTIONS[number];

// ============================================================
// ENTITIES
// ============================================================
@Entity('permissions')
export class Permission {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ length: 50 }) module: string;
  @Column({ length: 30 }) action: string;
  @Column({ nullable: true }) description: string;
}

@Entity('roles')
export class Role {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ name: 'tenant_id' }) tenantId: string;
  @Column({ length: 80 }) name: string;
  @Column({ type: 'text', nullable: true }) description: string;
  @Column({ name: 'is_system', default: false }) isSystem: boolean;

  @ManyToMany(() => Permission, { eager: true })
  @JoinTable({
    name: 'role_permissions',
    joinColumn: { name: 'role_id' },
    inverseJoinColumn: { name: 'permission_id' },
  })
  permissions: Permission[];

  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date;
}

@Entity('users')
export class UserEntity {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ name: 'tenant_id' }) tenantId: string;
  @Column({ name: 'role_id', nullable: true }) roleId: string;
  @Column({ length: 150 }) name: string;
  @Column({ length: 80 }) email: string;
  @Column({ name: 'password_hash' }) passwordHash: string;
  @Column({ name: 'is_owner', default: false }) isOwner: boolean;
  @Column({ default: 'active' }) status: string;
  @Column({ name: 'last_login_at', type: 'timestamptz', nullable: true }) lastLoginAt: Date;

  @ManyToOne(() => Role, { eager: true, nullable: true })
  @JoinColumn({ name: 'role_id' })
  role: Role;

  @ManyToMany(() => Permission, { eager: true })
  @JoinTable({
    name: 'user_permissions',
    joinColumn: { name: 'user_id' },
    inverseJoinColumn: { name: 'permission_id' },
  })
  extraPermissions: Permission[];

  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date;
}

// ============================================================
// HELPERS — verificação de permissão
// ============================================================
export function userHasPermission(
  user: UserEntity,
  module: string,
  action: string,
): boolean {
  if (user.isOwner) return true;

  // Permissões do perfil
  const rolePerms = user.role?.permissions || [];
  // Permissões extras/revogadas do usuário
  const extraPerms = user.extraPermissions || [];

  const key = `${module}:${action}`;

  // Verifica se foi explicitamente revogado no nível do usuário
  // (neste modelo simples, user_permissions só concede — para revogar, remover do perfil)
  const hasViaRole = rolePerms.some((p) => p.module === module && p.action === action);
  const hasViaExtra = extraPerms.some((p) => p.module === module && p.action === action);

  return hasViaRole || hasViaExtra;
}

// ============================================================
// DECORATOR + GUARD de permissão
// ============================================================
export const PERMISSION_KEY = 'required_permission';

export const RequirePermission = (module: KlavoModule, action: KlavoAction) =>
  SetMetadata(PERMISSION_KEY, { module, action });

@Injectable()
export class PermissionGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    @InjectRepository(UserEntity)
    private readonly userRepo: Repository<UserEntity>,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const required = this.reflector.getAllAndOverride<{ module: string; action: string }>(
      PERMISSION_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!required) return true;

    const { user: jwtPayload } = context.switchToHttp().getRequest();
    if (!jwtPayload) throw new ForbiddenException('Não autenticado');

    const user = await this.userRepo.findOne({
      where: { id: jwtPayload.userId, tenantId: jwtPayload.tenantId },
      relations: ['role', 'role.permissions', 'extraPermissions'],
    });

    if (!user || user.status !== 'active')
      throw new ForbiddenException('Usuário inativo ou não encontrado');

    const allowed = userHasPermission(user, required.module, required.action);
    if (!allowed)
      throw new ForbiddenException(
        `Sem permissão: ${required.module}:${required.action}`,
      );

    return true;
  }
}

// Guard combinado: JWT + Permissão
export const JwtPermissionGuards = (module: KlavoModule, action: KlavoAction) => [
  AuthGuard('jwt'),
  PermissionGuard,
];

// ============================================================
// DTOs
// ============================================================
export class CreateRoleDto {
  @ApiProperty({ example: 'Financeiro' })
  @IsString() @MinLength(2) name: string;

  @ApiPropertyOptional({ example: 'Acesso a cobranças e NFS-e' })
  @IsOptional() @IsString() description?: string;

  @ApiProperty({
    description: 'IDs das permissões concedidas',
    type: [String],
    example: ['uuid-permission-1', 'uuid-permission-2'],
  })
  @IsArray() @IsUUID('4', { each: true })
  permissionIds: string[];
}

export class UpdateRoleDto extends CreateRoleDto {}

export class AssignRoleDto {
  @ApiPropertyOptional({ description: 'ID do perfil a atribuir ao usuário; omitir ou null para remover' })
  @IsOptional() @IsUUID() roleId?: string | null;
}

export class SetUserPermissionsDto {
  @ApiProperty({
    description: 'IDs de permissões extras a conceder ao usuário (além do perfil)',
    type: [String],
  })
  @IsArray() @IsUUID('4', { each: true })
  permissionIds: string[];
}

export class CreateUserDto {
  @ApiProperty({ example: 'Maria Costa' })
  @IsString() @MinLength(2) name: string;

  @ApiProperty({ example: 'maria@empresa.com' })
  @IsString() email: string;

  @ApiProperty({ minLength: 6 })
  @IsString() @MinLength(6) password: string;

  @ApiPropertyOptional({ description: 'ID do perfil' })
  @IsOptional() @IsUUID() roleId?: string;

  @ApiPropertyOptional({ description: 'IDs das empresas que o usuário poderá acessar; omitir = apenas a empresa atual', type: [String] })
  @IsOptional() @IsArray() @IsUUID('4', { each: true }) tenantIds?: string[];
}

// ============================================================
// SERVICE: RbacService
// ============================================================
import * as bcrypt from 'bcrypt';

@Injectable()
export class RbacService {
  constructor(
    @InjectRepository(Role)
    private readonly roleRepo: Repository<Role>,

    @InjectRepository(Permission)
    private readonly permissionRepo: Repository<Permission>,

    @InjectRepository(UserEntity)
    private readonly userRepo: Repository<UserEntity>,

    @InjectRepository(UserTenantMembership)
    private readonly membershipRepo: Repository<UserTenantMembership>,
  ) {}

  // ----------------------------------------------------------
  // Permissões do sistema (seed)
  // ----------------------------------------------------------
  async listPermissions(): Promise<Permission[]> {
    return this.permissionRepo.find({ order: { module: 'ASC', action: 'ASC' } });
  }

  async listPermissionsByModule(): Promise<Record<string, Permission[]>> {
    const all = await this.listPermissions();
    return all.reduce((acc, p) => {
      if (!acc[p.module]) acc[p.module] = [];
      acc[p.module].push(p);
      return acc;
    }, {} as Record<string, Permission[]>);
  }

  // ----------------------------------------------------------
  // Perfis (Roles)
  // ----------------------------------------------------------
  async createRole(tenantId: string, dto: CreateRoleDto): Promise<Role> {
    const exists = await this.roleRepo.findOne({ where: { tenantId, name: dto.name } });
    if (exists) throw new ConflictException('Já existe um perfil com esse nome');

    const permissions = await this.permissionRepo.findByIds(dto.permissionIds);
    const role = this.roleRepo.create({ tenantId, name: dto.name, description: dto.description, permissions });
    return this.roleRepo.save(role);
  }

  async listRoles(tenantId: string): Promise<Role[]> {
    return this.roleRepo.find({
      where: { tenantId },
      relations: ['permissions'],
      order: { name: 'ASC' },
    });
  }

  async findRole(tenantId: string, id: string): Promise<Role> {
    const role = await this.roleRepo.findOne({
      where: { id, tenantId },
      relations: ['permissions'],
    });
    if (!role) throw new NotFoundException('Perfil não encontrado');
    return role;
  }

  async updateRole(tenantId: string, id: string, dto: UpdateRoleDto): Promise<Role> {
    const role = await this.findRole(tenantId, id);
    if (role.isSystem) throw new ForbiddenException('Perfis de sistema não podem ser editados');
    const permissions = await this.permissionRepo.findByIds(dto.permissionIds);
    role.name = dto.name;
    role.description = dto.description;
    role.permissions = permissions;
    return this.roleRepo.save(role);
  }

  async deleteRole(tenantId: string, id: string): Promise<void> {
    const role = await this.findRole(tenantId, id);
    if (role.isSystem) throw new ForbiddenException('Perfis de sistema não podem ser excluídos');
    await this.roleRepo.remove(role);
  }

  // ----------------------------------------------------------
  // Usuários
  // ----------------------------------------------------------
  async createUser(currentTenantId: string, dto: CreateUserDto): Promise<UserEntity> {
    const tenantIds = dto.tenantIds?.length ? dto.tenantIds : [currentTenantId];

    for (const tid of tenantIds) {
      const exists = await this.userRepo.findOne({ where: { tenantId: tid, email: dto.email } });
      if (exists) throw new ConflictException(`E-mail já cadastrado${tenantIds.length > 1 ? ` na empresa ${tid}` : ''}`);
    }

    const passwordHash = await bcrypt.hash(dto.password, 10);
    let primaryUser: UserEntity | undefined;

    for (const tid of tenantIds) {
      const user = await this.userRepo.save(
        this.userRepo.create({ tenantId: tid, name: dto.name, email: dto.email, passwordHash, roleId: dto.roleId }),
      );
      if (tid === currentTenantId) primaryUser = user;

      // Garante que a membership existe para que o usuário possa trocar de empresa
      const existingMembership = await this.membershipRepo.findOne({ where: { email: dto.email, tenantId: tid } });
      if (!existingMembership) {
        await this.membershipRepo.save(
          this.membershipRepo.create({ email: dto.email, tenantId: tid, role: 'member' }),
        );
      }
    }

    return primaryUser ?? await this.userRepo.findOne({ where: { email: dto.email, tenantId: currentTenantId } });
  }

  async listUsers(tenantId: string): Promise<Omit<UserEntity, 'passwordHash'>[]> {
    const users = await this.userRepo.find({
      where: { tenantId },
      relations: ['role', 'extraPermissions'],
      order: { name: 'ASC' },
    });
    return users.map(({ passwordHash, ...u }) => u as any);
  }

  async findUser(tenantId: string, id: string): Promise<UserEntity> {
    const user = await this.userRepo.findOne({
      where: { id, tenantId },
      relations: ['role', 'role.permissions', 'extraPermissions'],
    });
    if (!user) throw new NotFoundException('Usuário não encontrado');
    return user;
  }

  async assignRole(tenantId: string, userId: string, dto: AssignRoleDto): Promise<UserEntity> {
    const user = await this.findUser(tenantId, userId);
    if (dto.roleId) {
      const role = await this.findRole(tenantId, dto.roleId);
      user.roleId = role.id;
      user.role = role;
    } else {
      user.roleId = null;
      user.role = null;
    }
    return this.userRepo.save(user);
  }

  async setUserExtraPermissions(
    tenantId: string,
    userId: string,
    dto: SetUserPermissionsDto,
  ): Promise<UserEntity> {
    const user = await this.findUser(tenantId, userId);
    const permissions = dto.permissionIds.length
      ? await this.permissionRepo.findByIds(dto.permissionIds)
      : [];
    user.extraPermissions = permissions;
    return this.userRepo.save(user);
  }

  async getUserEffectivePermissions(
    tenantId: string,
    userId: string,
  ): Promise<{ module: string; action: string; source: 'role' | 'user' }[]> {
    const user = await this.findUser(tenantId, userId);
    const result: { module: string; action: string; source: 'role' | 'user' }[] = [];

    if (user.isOwner) {
      const all = await this.listPermissions();
      return all.map((p) => ({ module: p.module, action: p.action, source: 'role' }));
    }

    for (const p of user.role?.permissions || []) {
      result.push({ module: p.module, action: p.action, source: 'role' });
    }
    for (const p of user.extraPermissions || []) {
      const already = result.find((r) => r.module === p.module && r.action === p.action);
      if (!already) result.push({ module: p.module, action: p.action, source: 'user' });
    }

    return result.sort((a, b) => a.module.localeCompare(b.module));
  }

  async toggleUserStatus(tenantId: string, userId: string): Promise<UserEntity> {
    const user = await this.findUser(tenantId, userId);
    if (user.isOwner) throw new ForbiddenException('O owner não pode ser desativado');
    user.status = user.status === 'active' ? 'inactive' : 'active';
    return this.userRepo.save(user);
  }
}

// ============================================================
// CONTROLLERS
// ============================================================
@ApiTags('Controle de Acesso — Permissões')
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'))
@Controller('rbac/permissions')
export class PermissionsController {
  constructor(private readonly rbacService: RbacService) {}

  @Get()
  @ApiOperation({ summary: 'Listar todas as permissões disponíveis agrupadas por módulo' })
  listByModule() {
    return this.rbacService.listPermissionsByModule();
  }
}

@ApiTags('Controle de Acesso — Perfis')
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'))
@Controller('rbac/roles')
export class RolesController {
  constructor(private readonly rbacService: RbacService) {}

  @Post()
  @ApiOperation({ summary: 'Criar perfil de acesso customizado' })
  create(@Body() dto: CreateRoleDto, @Request() req: any) {
    return this.rbacService.createRole(req.user.tenantId, dto);
  }

  @Get()
  @ApiOperation({ summary: 'Listar perfis do tenant' })
  list(@Request() req: any) {
    return this.rbacService.listRoles(req.user.tenantId);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Detalhar perfil' })
  findOne(@Param('id') id: string, @Request() req: any) {
    return this.rbacService.findRole(req.user.tenantId, id);
  }

  @Put(':id')
  @ApiOperation({ summary: 'Atualizar perfil e suas permissões' })
  update(@Param('id') id: string, @Body() dto: UpdateRoleDto, @Request() req: any) {
    return this.rbacService.updateRole(req.user.tenantId, id, dto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({ summary: 'Excluir perfil' })
  remove(@Param('id') id: string, @Request() req: any) {
    return this.rbacService.deleteRole(req.user.tenantId, id);
  }
}

@ApiTags('Controle de Acesso — Usuários')
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'))
@Controller('rbac/users')
export class UsersController {
  constructor(private readonly rbacService: RbacService) {}

  @Post()
  @ApiOperation({ summary: 'Criar novo usuário no tenant' })
  create(@Body() dto: CreateUserDto, @Request() req: any) {
    return this.rbacService.createUser(req.user.tenantId, dto);
  }

  @Get()
  @ApiOperation({ summary: 'Listar usuários do tenant' })
  list(@Request() req: any) {
    return this.rbacService.listUsers(req.user.tenantId);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Detalhar usuário' })
  findOne(@Param('id') id: string, @Request() req: any) {
    return this.rbacService.findUser(req.user.tenantId, id);
  }

  @Patch(':id/role')
  @ApiOperation({ summary: 'Atribuir perfil ao usuário' })
  assignRole(@Param('id') id: string, @Body() dto: AssignRoleDto, @Request() req: any) {
    return this.rbacService.assignRole(req.user.tenantId, id, dto);
  }

  @Patch(':id/permissions')
  @ApiOperation({ summary: 'Definir permissões extras do usuário (além do perfil)' })
  setPermissions(@Param('id') id: string, @Body() dto: SetUserPermissionsDto, @Request() req: any) {
    return this.rbacService.setUserExtraPermissions(req.user.tenantId, id, dto);
  }

  @Get(':id/effective-permissions')
  @ApiOperation({ summary: 'Ver permissões efetivas do usuário (perfil + extras)' })
  effectivePermissions(@Param('id') id: string, @Request() req: any) {
    return this.rbacService.getUserEffectivePermissions(req.user.tenantId, id);
  }

  @Patch(':id/toggle-status')
  @ApiOperation({ summary: 'Ativar/inativar usuário' })
  toggleStatus(@Param('id') id: string, @Request() req: any) {
    return this.rbacService.toggleUserStatus(req.user.tenantId, id);
  }
}

// ============================================================
// MODULE
// ============================================================
@Module({
  imports: [TypeOrmModule.forFeature([Role, Permission, UserEntity, UserTenantMembership])],
  controllers: [PermissionsController, RolesController, UsersController],
  providers: [RbacService, PermissionGuard],
  exports: [RbacService, PermissionGuard],
})
export class RbacModule {}
