/**
 * RetentionPolicy Service
 * Audit log retention and cleanup management.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import { AuditLog } from '../entities/audit-log.entity';
import { EntityChange } from '../entities/entity-change.entity';
import { LoginHistory } from '../entities/login-history.entity';
import { SensitiveDataAccess } from '../entities/sensitive-data-access.entity';
import { DataExport } from '../entities/data-export.entity';
import { PermissionChange } from '../entities/permission-change.entity';
import { ConfigChange } from '../entities/config-change.entity';
import { ServiceContext } from './audit-log.service';

export interface RetentionPolicy {
  auditLogs: number; // days
  entityChanges: number; // days
  loginHistory: number; // days
  sensitiveDataAccess: number; // days
  dataExports: number; // days
  permissionChanges: number; // days
  configChanges: number; // days
}

export interface CleanupResult {
  auditLogs: number;
  entityChanges: number;
  loginHistory: number;
  sensitiveDataAccess: number;
  dataExports: number;
  permissionChanges: number;
  configChanges: number;
  totalDeleted: number;
}

export interface StorageStats {
  auditLogs: { count: number; oldestRecord: Date | null };
  entityChanges: { count: number; oldestRecord: Date | null };
  loginHistory: { count: number; oldestRecord: Date | null };
  sensitiveDataAccess: { count: number; oldestRecord: Date | null };
  dataExports: { count: number; oldestRecord: Date | null };
  permissionChanges: { count: number; oldestRecord: Date | null };
  configChanges: { count: number; oldestRecord: Date | null };
  totalRecords: number;
}

const DEFAULT_RETENTION_POLICY: RetentionPolicy = {
  auditLogs: 365,           // 1 year
  entityChanges: 730,       // 2 years
  loginHistory: 180,        // 6 months
  sensitiveDataAccess: 1825, // 5 years (compliance)
  dataExports: 90,          // 3 months
  permissionChanges: 1825,  // 5 years (compliance)
  configChanges: 1095,      // 3 years
};

export class RetentionPolicyService {
  private policy: RetentionPolicy;

  constructor(
    private readonly auditLogRepository: Repository<AuditLog>,
    private readonly entityChangeRepository: Repository<EntityChange>,
    private readonly loginHistoryRepository: Repository<LoginHistory>,
    private readonly sensitiveDataAccessRepository: Repository<SensitiveDataAccess>,
    private readonly dataExportRepository: Repository<DataExport>,
    private readonly permissionChangeRepository: Repository<PermissionChange>,
    private readonly configChangeRepository: Repository<ConfigChange>,
    policy?: Partial<RetentionPolicy>,
  ) {
    this.policy = { ...DEFAULT_RETENTION_POLICY, ...policy };
  }

  /**
   * Get current retention policy
   */
  getPolicy(): RetentionPolicy {
    return { ...this.policy };
  }

  /**
   * Update retention policy
   */
  updatePolicy(updates: Partial<RetentionPolicy>): RetentionPolicy {
    this.policy = { ...this.policy, ...updates };
    return this.getPolicy();
  }

  /**
   * Run cleanup for all audit tables based on retention policy
   */
  async runCleanup(ctx: ServiceContext): Promise<CleanupResult> {
    const now = new Date();

    const [
      auditLogs,
      entityChanges,
      loginHistory,
      sensitiveDataAccess,
      dataExports,
      permissionChanges,
      configChanges,
    ] = await Promise.all([
      this.cleanupAuditLogs(ctx, now),
      this.cleanupEntityChanges(ctx, now),
      this.cleanupLoginHistory(ctx, now),
      this.cleanupSensitiveDataAccess(ctx, now),
      this.cleanupDataExports(ctx, now),
      this.cleanupPermissionChanges(ctx, now),
      this.cleanupConfigChanges(ctx, now),
    ]);

    return {
      auditLogs,
      entityChanges,
      loginHistory,
      sensitiveDataAccess,
      dataExports,
      permissionChanges,
      configChanges,
      totalDeleted: auditLogs + entityChanges + loginHistory +
        sensitiveDataAccess + dataExports + permissionChanges + configChanges,
    };
  }

  /**
   * Cleanup audit logs older than retention period
   */
  private async cleanupAuditLogs(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.auditLogs);

    const result = await this.auditLogRepository
      .createQueryBuilder()
      .delete()
      .from(AuditLog)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('created_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Cleanup entity changes older than retention period
   */
  private async cleanupEntityChanges(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.entityChanges);

    const result = await this.entityChangeRepository
      .createQueryBuilder()
      .delete()
      .from(EntityChange)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('changed_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Cleanup login history older than retention period
   */
  private async cleanupLoginHistory(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.loginHistory);

    const result = await this.loginHistoryRepository
      .createQueryBuilder()
      .delete()
      .from(LoginHistory)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('attempted_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Cleanup sensitive data access logs older than retention period
   */
  private async cleanupSensitiveDataAccess(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.sensitiveDataAccess);

    const result = await this.sensitiveDataAccessRepository
      .createQueryBuilder()
      .delete()
      .from(SensitiveDataAccess)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('accessed_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Cleanup data exports older than retention period
   */
  private async cleanupDataExports(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.dataExports);

    const result = await this.dataExportRepository
      .createQueryBuilder()
      .delete()
      .from(DataExport)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('requested_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Cleanup permission changes older than retention period
   */
  private async cleanupPermissionChanges(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.permissionChanges);

    const result = await this.permissionChangeRepository
      .createQueryBuilder()
      .delete()
      .from(PermissionChange)
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('changed_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Cleanup config changes older than retention period
   */
  private async cleanupConfigChanges(ctx: ServiceContext, now: Date): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - this.policy.configChanges);

    const result = await this.configChangeRepository
      .createQueryBuilder()
      .delete()
      .from(ConfigChange)
      .where('tenant_id = :tenantId OR tenant_id IS NULL', { tenantId: ctx.tenantId })
      .andWhere('changed_at < :cutoffDate', { cutoffDate })
      .execute();

    return result.affected || 0;
  }

  /**
   * Get storage statistics for all audit tables
   */
  async getStorageStats(ctx: ServiceContext): Promise<StorageStats> {
    const [
      auditLogs,
      entityChanges,
      loginHistory,
      sensitiveDataAccess,
      dataExports,
      permissionChanges,
      configChanges,
    ] = await Promise.all([
      this.getTableStats(this.auditLogRepository, ctx.tenantId, 'created_at'),
      this.getTableStats(this.entityChangeRepository, ctx.tenantId, 'changed_at'),
      this.getTableStats(this.loginHistoryRepository, ctx.tenantId, 'attempted_at'),
      this.getTableStats(this.sensitiveDataAccessRepository, ctx.tenantId, 'accessed_at'),
      this.getTableStats(this.dataExportRepository, ctx.tenantId, 'requested_at'),
      this.getTableStats(this.permissionChangeRepository, ctx.tenantId, 'changed_at'),
      this.getTableStats(this.configChangeRepository, ctx.tenantId, 'changed_at', true),
    ]);

    return {
      auditLogs,
      entityChanges,
      loginHistory,
      sensitiveDataAccess,
      dataExports,
      permissionChanges,
      configChanges,
      totalRecords: auditLogs.count + entityChanges.count + loginHistory.count +
        sensitiveDataAccess.count + dataExports.count + permissionChanges.count + configChanges.count,
    };
  }

  /**
   * Get statistics for a single table
   */
  private async getTableStats(
    repository: Repository<any>,
    tenantId: string,
    dateColumn: string,
    includeNullTenant = false,
  ): Promise<{ count: number; oldestRecord: Date | null }> {
    const qb = repository.createQueryBuilder('t');

    if (includeNullTenant) {
      qb.where('t.tenant_id = :tenantId OR t.tenant_id IS NULL', { tenantId });
    } else {
      qb.where('t.tenant_id = :tenantId', { tenantId });
    }

    const [count, oldest] = await Promise.all([
      qb.clone().getCount(),
      qb.clone()
        .select(`MIN(t.${dateColumn})`, 'oldest')
        .getRawOne(),
    ]);

    return {
      count,
      oldestRecord: oldest?.oldest ? new Date(oldest.oldest) : null,
    };
  }

  /**
   * Preview what would be deleted by cleanup
   */
  async previewCleanup(ctx: ServiceContext): Promise<CleanupResult> {
    const now = new Date();

    const [
      auditLogs,
      entityChanges,
      loginHistory,
      sensitiveDataAccess,
      dataExports,
      permissionChanges,
      configChanges,
    ] = await Promise.all([
      this.countExpiredRecords(this.auditLogRepository, ctx.tenantId, 'created_at', this.policy.auditLogs, now),
      this.countExpiredRecords(this.entityChangeRepository, ctx.tenantId, 'changed_at', this.policy.entityChanges, now),
      this.countExpiredRecords(this.loginHistoryRepository, ctx.tenantId, 'attempted_at', this.policy.loginHistory, now),
      this.countExpiredRecords(this.sensitiveDataAccessRepository, ctx.tenantId, 'accessed_at', this.policy.sensitiveDataAccess, now),
      this.countExpiredRecords(this.dataExportRepository, ctx.tenantId, 'requested_at', this.policy.dataExports, now),
      this.countExpiredRecords(this.permissionChangeRepository, ctx.tenantId, 'changed_at', this.policy.permissionChanges, now),
      this.countExpiredRecords(this.configChangeRepository, ctx.tenantId, 'changed_at', this.policy.configChanges, now, true),
    ]);

    return {
      auditLogs,
      entityChanges,
      loginHistory,
      sensitiveDataAccess,
      dataExports,
      permissionChanges,
      configChanges,
      totalDeleted: auditLogs + entityChanges + loginHistory +
        sensitiveDataAccess + dataExports + permissionChanges + configChanges,
    };
  }

  /**
   * Count records that would be deleted
   */
  private async countExpiredRecords(
    repository: Repository<any>,
    tenantId: string,
    dateColumn: string,
    retentionDays: number,
    now: Date,
    includeNullTenant = false,
  ): Promise<number> {
    const cutoffDate = new Date(now);
    cutoffDate.setDate(cutoffDate.getDate() - retentionDays);

    const qb = repository.createQueryBuilder('t');

    if (includeNullTenant) {
      qb.where('t.tenant_id = :tenantId OR t.tenant_id IS NULL', { tenantId });
    } else {
      qb.where('t.tenant_id = :tenantId', { tenantId });
    }

    qb.andWhere(`t.${dateColumn} < :cutoffDate`, { cutoffDate });

    return qb.getCount();
  }
}
