import { Injectable, NotFoundException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Permission } from './entities/permission.entity';
import { CreatePermissionDto } from './dto/create-permission.dto';
import { UpdatePermissionDto } from './dto/update-permission.dto';
import { ModuleEntity } from '../modules/entities/module.entity';
import { v4 as uuid } from 'uuid';

@Injectable()
export class PermissionService {
  constructor(
    @InjectRepository(Permission, 'scheme_dbs')
    private readonly permissionRepo: Repository<Permission>,
    @InjectRepository(ModuleEntity, 'scheme_dbs')
    private readonly moduleRepo: Repository<ModuleEntity>,
  ) {}

  async create(
    dto: CreatePermissionDto,
    unique: boolean = false,
  ): Promise<Permission> {
    const permissionName = unique
      ? `${dto.name}:${uuid()}` // unique permission
      : dto.name; // standard shared permission

    const module = await this.moduleRepo.findOne({
      where: { id: dto.module_id },
    });
    if (!module) {
      throw new NotFoundException(`Module with ID ${dto.module_id} not found`);
    }

    const permission = this.permissionRepo.create({
      name: permissionName,
      action: dto.action,
      description: dto.description,
      module: module,
    });

    return this.permissionRepo.save(permission);
  }

  async findAll(): Promise<Permission[]> {
    return this.permissionRepo.find({ relations: ['module'] });
  }

  async findOne(id: number): Promise<Permission> {
    const permission = await this.permissionRepo.findOne({
      where: { id },
      relations: ['module'],
    });

    if (!permission) {
      throw new NotFoundException(`Permission with ID ${id} not found`);
    }

    return permission;
  }

  async update(id: number, dto: UpdatePermissionDto): Promise<Permission> {
    const permission = await this.findOne(id);

    if (dto.name !== undefined) {
      permission.name = dto.name;
    }
    if (dto.action !== undefined) {
      permission.action = dto.action;
    }
    if (dto.description !== undefined) {
      permission.description = dto.description;
    }
    if (dto.module_id !== undefined) {
      const module = await this.moduleRepo.findOne({
        where: { id: dto.module_id },
      });
      if (!module) {
        throw new NotFoundException(
          `Module with ID ${dto.module_id} not found`,
        );
      }
      permission.module = module;
    }

    return this.permissionRepo.save(permission);
  }

  async remove(id: number): Promise<void> {
    const permission = await this.findOne(id);
    await this.permissionRepo.remove(permission);
  }

  async findByIds(ids: number[]): Promise<Permission[]> {
    return this.permissionRepo.findByIds(ids);
  }

  async checkUserPermission(
    userId: number,
    moduleSlug: string,
    action: string,
    recordData?: any,
  ): Promise<boolean> {
    // 1️⃣ Check DIRECT user permission
    const directPerm = await this.permissionRepo
      .createQueryBuilder('p')
      .innerJoin('user_permissions', 'up', 'up.permission_id = p.id')
      .innerJoin('p.module', 'm')
      .leftJoin(
        'user_permission_conditions',
        'c',
        'c.user_permission_id = up.id',
      )
      .where('up.user_id = :userId', { userId })
      .andWhere('m.slug = :moduleSlug', { moduleSlug })
      .andWhere('p.action = :action', { action })
      .select(['p.id', 'c.condition_field', 'c.condition_value', 'c.logic'])
      .getRawMany();

    if (directPerm.length > 0) {
      // Has direct permission - check conditions
      const conditions = directPerm.filter((p) => p.c_condition_field);
      return this.evaluateConditions(conditions, recordData, userId);
    }

    // 2️⃣ Check ROLE-based permission
    const rolePerm = await this.permissionRepo
      .createQueryBuilder('p')
      .innerJoin('role_permissions', 'rp', 'rp.permission_id = p.id')
      .innerJoin('user_roles', 'ur', 'ur.role_id = rp.role_id')
      .innerJoin('p.module', 'm')
      .where('ur.user_id = :userId', { userId })
      .andWhere('m.slug = :moduleSlug', { moduleSlug })
      .andWhere('p.action = :action', { action })
      .getOne();

    if (rolePerm) {
      return true; // Role permission = full access
    }

    // 3️⃣ No permission
    return false;
  }

  /**
   * Evaluate conditions
   */
  private evaluateConditions(
    conditions: any[],
    recordData: any,
    userId: number,
  ): boolean {
    if (!conditions || conditions.length === 0) return true;
    if (!recordData) return true;

    for (const cond of conditions) {
      let expected = cond.c_condition_value;
      if (expected === '{{user_id}}') expected = String(userId);

      const actual = String(recordData[cond.c_condition_field] || '');

      if (actual !== expected) {
        if (cond.c_logic === 'AND' || !cond.c_logic) return false;
      } else {
        if (cond.c_logic === 'OR') return true;
      }
    }
    return true;
  }
  async getUserPermissions(userId: number): Promise<any[]> {
  // 1️⃣ Direct user permissions
  const directPerms = await this.permissionRepo
    .createQueryBuilder('p')
    .innerJoin('user_permissions', 'up', 'up.permission_id = p.id')
    .innerJoin('p.module', 'm')
    .leftJoin(
      'user_permission_conditions',
      'c',
      'c.user_permission_id = up.id',
    )
    .where('up.user_id = :userId', { userId })
    .select([
      'p.id',
      'p.name',
      'p.action',
      'm.slug AS moduleSlug',
      'c.condition_field',
      'c.condition_value',
      'c.logic',
    ])
    .getRawMany();

  // 2️⃣ Role-based permissions
  const rolePerms = await this.permissionRepo
    .createQueryBuilder('p')
    .innerJoin('role_permissions', 'rp', 'rp.permission_id = p.id')
    .innerJoin('user_roles', 'ur', 'ur.role_id = rp.role_id')
    .innerJoin('p.module', 'm')
    .where('ur.user_id = :userId', { userId })
    .select(['p.id', 'p.name', 'p.action', 'm.slug AS moduleSlug'])
    .getRawMany();

  // 3️⃣ Combine and return
  return [...directPerms, ...rolePerms];
}
// Add these methods to your PermissionService

async findModuleByUrlSegment(urlSegment: string): Promise<ModuleEntity | null> {
  // Try matching by slug first, then by api_prefix
  const module = await this.moduleRepo
    .createQueryBuilder('m')
    .where('m.slug = :segment', { segment: urlSegment })
    .orWhere('m.api_prefix = :segment', { segment: urlSegment })
    .getOne();

  return module || null;
}

async hasAnyPermissionOnModule(userId: number, moduleSlug: string): Promise<boolean> {
  // Check direct permissions
  const directCount = await this.permissionRepo
    .createQueryBuilder('p')
    .innerJoin('user_permissions', 'up', 'up.permission_id = p.id')
    .innerJoin('p.module', 'm')
    .where('up.user_id = :userId', { userId })
    .andWhere('m.slug = :moduleSlug', { moduleSlug })
    .getCount();

  if (directCount > 0) return true;

  // Check role permissions
  const roleCount = await this.permissionRepo
    .createQueryBuilder('p')
    .innerJoin('role_permissions', 'rp', 'rp.permission_id = p.id')
    .innerJoin('user_roles', 'ur', 'ur.role_id = rp.role_id')
    .innerJoin('p.module', 'm')
    .where('ur.user_id = :userId', { userId })
    .andWhere('m.slug = :moduleSlug', { moduleSlug })
    .getCount();

  return roleCount > 0;
}

async getUserPermissionsForModule(userId: number, moduleSlug: string): Promise<string[]> {
  // Get all actions this user can perform on this module
  const direct = await this.permissionRepo
    .createQueryBuilder('p')
    .innerJoin('user_permissions', 'up', 'up.permission_id = p.id')
    .innerJoin('p.module', 'm')
    .where('up.user_id = :userId', { userId })
    .andWhere('m.slug = :moduleSlug', { moduleSlug })
    .select('DISTINCT p.action', 'action')
    .getRawMany();

  const role = await this.permissionRepo
    .createQueryBuilder('p')
    .innerJoin('role_permissions', 'rp', 'rp.permission_id = p.id')
    .innerJoin('user_roles', 'ur', 'ur.role_id = rp.role_id')
    .innerJoin('p.module', 'm')
    .where('ur.user_id = :userId', { userId })
    .andWhere('m.slug = :moduleSlug', { moduleSlug })
    .select('DISTINCT p.action', 'action')
    .getRawMany();

  const actions = [...new Set([...direct, ...role].map((r) => r.action))];
  return actions;
}
}
