/**
 * SensitiveDataAccess Service
 * Security/compliance logging for PII, financial, medical and credential access.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import {
  SensitiveDataAccess,
  DataType,
  AccessType,
} from '../entities/sensitive-data-access.entity';
import { ServiceContext, PaginatedResult } from './audit-log.service';

export interface CreateSensitiveDataAccessDto {
  userId: string;
  sessionId?: string;
  dataType: DataType;
  dataCategory?: string;
  entityType?: string;
  entityId?: string;
  accessType: AccessType;
  accessReason?: string;
  ipAddress?: string;
  userAgent?: string;
  wasAuthorized?: boolean;
  denialReason?: string;
}

export interface SensitiveDataAccessFilters {
  userId?: string;
  dataType?: DataType;
  dataCategory?: string;
  entityType?: string;
  entityId?: string;
  accessType?: AccessType;
  wasAuthorized?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface SensitiveDataStats {
  period: { days: number; from: Date; to: Date };
  totalAccess: number;
  authorizedAccess: number;
  deniedAccess: number;
  byDataType: { dataType: string; count: number }[];
  byAccessType: { accessType: string; count: number }[];
  topUsers: { userId: string; count: number }[];
}

export class SensitiveDataAccessService {
  constructor(private readonly repository: Repository<SensitiveDataAccess>) {}

  /**
   * Log sensitive data access
   */
  async log(
    ctx: ServiceContext,
    dto: CreateSensitiveDataAccessDto,
  ): Promise<SensitiveDataAccess> {
    const access = this.repository.create({
      tenantId: ctx.tenantId,
      userId: dto.userId,
      sessionId: dto.sessionId,
      dataType: dto.dataType,
      dataCategory: dto.dataCategory,
      entityType: dto.entityType,
      entityId: dto.entityId,
      accessType: dto.accessType,
      accessReason: dto.accessReason,
      ipAddress: dto.ipAddress,
      userAgent: dto.userAgent,
      wasAuthorized: dto.wasAuthorized ?? true,
      denialReason: dto.denialReason,
    });

    return this.repository.save(access);
  }

  /**
   * Find access logs with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: SensitiveDataAccessFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<SensitiveDataAccess>> {
    const qb = this.repository
      .createQueryBuilder('sda')
      .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('sda.user_id = :userId', { userId: filters.userId });
    }
    if (filters.dataType) {
      qb.andWhere('sda.data_type = :dataType', { dataType: filters.dataType });
    }
    if (filters.dataCategory) {
      qb.andWhere('sda.data_category = :dataCategory', { dataCategory: filters.dataCategory });
    }
    if (filters.entityType) {
      qb.andWhere('sda.entity_type = :entityType', { entityType: filters.entityType });
    }
    if (filters.entityId) {
      qb.andWhere('sda.entity_id = :entityId', { entityId: filters.entityId });
    }
    if (filters.accessType) {
      qb.andWhere('sda.access_type = :accessType', { accessType: filters.accessType });
    }
    if (filters.wasAuthorized !== undefined) {
      qb.andWhere('sda.was_authorized = :wasAuthorized', { wasAuthorized: filters.wasAuthorized });
    }
    if (filters.dateFrom) {
      qb.andWhere('sda.accessed_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('sda.accessed_at <= :dateTo', { dateTo: filters.dateTo });
    }

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

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

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

  /**
   * Get access logs for a user
   */
  async findByUser(
    ctx: ServiceContext,
    userId: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<SensitiveDataAccess>> {
    return this.findWithFilters(ctx, { userId }, page, limit);
  }

  /**
   * Get access logs for an entity
   */
  async findByEntity(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<SensitiveDataAccess>> {
    return this.findWithFilters(ctx, { entityType, entityId }, page, limit);
  }

  /**
   * Get denied access attempts
   */
  async getDeniedAccess(
    ctx: ServiceContext,
    days = 7,
    limit = 100,
  ): Promise<SensitiveDataAccess[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

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

  /**
   * Get access logs by data type
   */
  async findByDataType(
    ctx: ServiceContext,
    dataType: DataType,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<SensitiveDataAccess>> {
    return this.findWithFilters(ctx, { dataType }, page, limit);
  }

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

    const [
      totalAccess,
      authorizedAccess,
      deniedAccess,
      byDataType,
      byAccessType,
      topUsers,
    ] = await Promise.all([
      this.repository
        .createQueryBuilder('sda')
        .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
        .getCount(),
      this.repository
        .createQueryBuilder('sda')
        .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
        .andWhere('sda.was_authorized = true')
        .getCount(),
      this.repository
        .createQueryBuilder('sda')
        .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
        .andWhere('sda.was_authorized = false')
        .getCount(),
      this.repository
        .createQueryBuilder('sda')
        .select('sda.data_type', 'dataType')
        .addSelect('COUNT(*)', 'count')
        .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
        .groupBy('sda.data_type')
        .getRawMany(),
      this.repository
        .createQueryBuilder('sda')
        .select('sda.access_type', 'accessType')
        .addSelect('COUNT(*)', 'count')
        .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
        .groupBy('sda.access_type')
        .getRawMany(),
      this.repository
        .createQueryBuilder('sda')
        .select('sda.user_id', 'userId')
        .addSelect('COUNT(*)', 'count')
        .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
        .groupBy('sda.user_id')
        .orderBy('count', 'DESC')
        .limit(10)
        .getRawMany(),
    ]);

    return {
      period: { days, from: dateFrom, to: new Date() },
      totalAccess,
      authorizedAccess,
      deniedAccess,
      byDataType: byDataType.map((r) => ({ dataType: r.dataType, count: parseInt(r.count) })),
      byAccessType: byAccessType.map((r) => ({ accessType: r.accessType, count: parseInt(r.count) })),
      topUsers: topUsers.map((r) => ({ userId: r.userId, count: parseInt(r.count) })),
    };
  }

  /**
   * Check if user has accessed specific data recently
   */
  async hasRecentAccess(
    ctx: ServiceContext,
    userId: string,
    entityType: string,
    entityId: string,
    hoursBack = 24,
  ): Promise<boolean> {
    const dateFrom = new Date();
    dateFrom.setHours(dateFrom.getHours() - hoursBack);

    const count = await this.repository
      .createQueryBuilder('sda')
      .where('sda.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sda.user_id = :userId', { userId })
      .andWhere('sda.entity_type = :entityType', { entityType })
      .andWhere('sda.entity_id = :entityId', { entityId })
      .andWhere('sda.accessed_at >= :dateFrom', { dateFrom })
      .andWhere('sda.was_authorized = true')
      .getCount();

    return count > 0;
  }
}
