/**
 * PermissionChange Service
 * Access control change auditing.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import {
  PermissionChange,
  PermissionChangeType,
  PermissionScope,
} from '../entities/permission-change.entity';
import { ServiceContext, PaginatedResult } from './audit-log.service';

export interface CreatePermissionChangeDto {
  changedBy: string;
  targetUserId: string;
  targetUserEmail?: string;
  changeType: PermissionChangeType;
  roleId?: string;
  roleCode?: string;
  permissionId?: string;
  permissionCode?: string;
  branchId?: string;
  scope?: PermissionScope;
  previousRoles?: string[];
  previousPermissions?: string[];
  reason?: string;
}

export interface PermissionChangeFilters {
  changedBy?: string;
  targetUserId?: string;
  changeType?: PermissionChangeType;
  roleCode?: string;
  permissionCode?: string;
  scope?: PermissionScope;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface PermissionChangeStats {
  period: { days: number; from: Date; to: Date };
  totalChanges: number;
  byChangeType: { changeType: string; count: number }[];
  byScope: { scope: string; count: number }[];
  topChangedUsers: { userId: string; email: string; count: number }[];
  topChangers: { userId: string; count: number }[];
}

export class PermissionChangeService {
  constructor(private readonly repository: Repository<PermissionChange>) {}

  /**
   * Record a permission change
   */
  async log(
    ctx: ServiceContext,
    dto: CreatePermissionChangeDto,
  ): Promise<PermissionChange> {
    const change = this.repository.create({
      tenantId: ctx.tenantId,
      changedBy: dto.changedBy,
      targetUserId: dto.targetUserId,
      targetUserEmail: dto.targetUserEmail,
      changeType: dto.changeType,
      roleId: dto.roleId,
      roleCode: dto.roleCode,
      permissionId: dto.permissionId,
      permissionCode: dto.permissionCode,
      branchId: dto.branchId,
      scope: dto.scope || 'tenant',
      previousRoles: dto.previousRoles,
      previousPermissions: dto.previousPermissions,
      reason: dto.reason,
    });

    return this.repository.save(change);
  }

  /**
   * Find changes with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: PermissionChangeFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<PermissionChange>> {
    const qb = this.repository
      .createQueryBuilder('pc')
      .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.changedBy) {
      qb.andWhere('pc.changed_by = :changedBy', { changedBy: filters.changedBy });
    }
    if (filters.targetUserId) {
      qb.andWhere('pc.target_user_id = :targetUserId', { targetUserId: filters.targetUserId });
    }
    if (filters.changeType) {
      qb.andWhere('pc.change_type = :changeType', { changeType: filters.changeType });
    }
    if (filters.roleCode) {
      qb.andWhere('pc.role_code = :roleCode', { roleCode: filters.roleCode });
    }
    if (filters.permissionCode) {
      qb.andWhere('pc.permission_code = :permissionCode', { permissionCode: filters.permissionCode });
    }
    if (filters.scope) {
      qb.andWhere('pc.scope = :scope', { scope: filters.scope });
    }
    if (filters.dateFrom) {
      qb.andWhere('pc.changed_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('pc.changed_at <= :dateTo', { dateTo: filters.dateTo });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('pc.changed_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get changes for a target user
   */
  async findByTargetUser(
    ctx: ServiceContext,
    targetUserId: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<PermissionChange>> {
    return this.findWithFilters(ctx, { targetUserId }, page, limit);
  }

  /**
   * Get changes made by a user
   */
  async findByChanger(
    ctx: ServiceContext,
    changedBy: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<PermissionChange>> {
    return this.findWithFilters(ctx, { changedBy }, page, limit);
  }

  /**
   * Get recent permission changes
   */
  async getRecentChanges(
    ctx: ServiceContext,
    days = 7,
    limit = 100,
  ): Promise<PermissionChange[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    return this.repository
      .createQueryBuilder('pc')
      .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('pc.changed_at >= :dateFrom', { dateFrom })
      .orderBy('pc.changed_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get role assignment/revocation history
   */
  async getRoleChanges(
    ctx: ServiceContext,
    roleCode: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<PermissionChange>> {
    const qb = this.repository
      .createQueryBuilder('pc')
      .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('pc.role_code = :roleCode', { roleCode })
      .andWhere('pc.change_type IN (:...types)', { types: ['role_assigned', 'role_revoked'] });

    const skip = (page - 1) * limit;
    qb.orderBy('pc.changed_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get permission grant/revocation history
   */
  async getPermissionChanges(
    ctx: ServiceContext,
    permissionCode: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<PermissionChange>> {
    const qb = this.repository
      .createQueryBuilder('pc')
      .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('pc.permission_code = :permissionCode', { permissionCode })
      .andWhere('pc.change_type IN (:...types)', { types: ['permission_granted', 'permission_revoked'] });

    const skip = (page - 1) * limit;
    qb.orderBy('pc.changed_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get statistics
   */
  async getStats(ctx: ServiceContext, days = 30): Promise<PermissionChangeStats> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const [
      totalChanges,
      byChangeType,
      byScope,
      topChangedUsers,
      topChangers,
    ] = await Promise.all([
      this.repository
        .createQueryBuilder('pc')
        .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('pc.changed_at >= :dateFrom', { dateFrom })
        .getCount(),
      this.repository
        .createQueryBuilder('pc')
        .select('pc.change_type', 'changeType')
        .addSelect('COUNT(*)', 'count')
        .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('pc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('pc.change_type')
        .getRawMany(),
      this.repository
        .createQueryBuilder('pc')
        .select('pc.scope', 'scope')
        .addSelect('COUNT(*)', 'count')
        .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('pc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('pc.scope')
        .getRawMany(),
      this.repository
        .createQueryBuilder('pc')
        .select('pc.target_user_id', 'userId')
        .addSelect('pc.target_user_email', 'email')
        .addSelect('COUNT(*)', 'count')
        .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('pc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('pc.target_user_id')
        .addGroupBy('pc.target_user_email')
        .orderBy('count', 'DESC')
        .limit(10)
        .getRawMany(),
      this.repository
        .createQueryBuilder('pc')
        .select('pc.changed_by', 'userId')
        .addSelect('COUNT(*)', 'count')
        .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('pc.changed_at >= :dateFrom', { dateFrom })
        .groupBy('pc.changed_by')
        .orderBy('count', 'DESC')
        .limit(10)
        .getRawMany(),
    ]);

    return {
      period: { days, from: dateFrom, to: new Date() },
      totalChanges,
      byChangeType: byChangeType.map((r) => ({ changeType: r.changeType, count: parseInt(r.count) })),
      byScope: byScope.map((r) => ({ scope: r.scope || 'tenant', count: parseInt(r.count) })),
      topChangedUsers: topChangedUsers.map((r) => ({
        userId: r.userId,
        email: r.email || '',
        count: parseInt(r.count),
      })),
      topChangers: topChangers.map((r) => ({ userId: r.userId, count: parseInt(r.count) })),
    };
  }

  /**
   * Get user's current effective permissions based on history
   */
  async getUserPermissionHistory(
    ctx: ServiceContext,
    targetUserId: string,
  ): Promise<PermissionChange[]> {
    return this.repository
      .createQueryBuilder('pc')
      .where('pc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('pc.target_user_id = :targetUserId', { targetUserId })
      .orderBy('pc.changed_at', 'DESC')
      .getMany();
  }
}
