import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThan, DataSource } from 'typeorm';

import { AggregatedMetric } from '../entities/aggregated-metric.entity';
import {
  getRetentionPolicies,
  getRevenueAnonymizationThresholdDays,
  type RetentionPolicy,
} from './retention-policies';

/**
 * Result of a data retention cleanup operation.
 */
export interface CleanupResult {
  /** Policy that was executed */
  policy: string;
  /** Number of records deleted */
  deletedCount: number;
  /** Number of records anonymized */
  anonymizedCount?: number;
  /** Timestamp of cutoff date */
  cutoffDate: Date;
  /** Duration of operation in milliseconds */
  durationMs: number;
  /** Any errors encountered */
  errors?: string[];
}

/**
 * Audit log entry for retention operations.
 */
export interface RetentionAuditLog {
  /** Timestamp of the operation */
  timestamp: Date;
  /** Type of operation (cleanup, anonymize) */
  operation: 'cleanup' | 'anonymize';
  /** Policy executed */
  policy: string;
  /** Number of records affected */
  recordsAffected: number;
  /** Cutoff date used */
  cutoffDate: Date;
  /** Duration in milliseconds */
  durationMs: number;
  /** Success status */
  success: boolean;
  /** Error message if failed */
  errorMessage?: string;
}

/**
 * GDPR-Compliant Data Retention Service
 *
 * Implements automated data retention policies with:
 * - Scheduled cleanup of expired data
 * - Anonymization for legally required retention
 * - Comprehensive audit logging
 * - Configurable retention periods
 *
 * Compliance Features:
 * - GDPR Article 5(1)(e): Storage limitation principle
 * - GDPR Article 17: Right to erasure (automated implementation)
 * - Tax law compliance: 7-year revenue retention with anonymization
 * - Audit trail: All retention operations are logged
 */
@Injectable()
export class DataRetentionService {
  private readonly logger = new Logger(DataRetentionService.name);
  private readonly policies = getRetentionPolicies();
  private readonly auditLogs: RetentionAuditLog[] = [];

  constructor(
    @InjectRepository(AggregatedMetric)
    private readonly aggregatedMetricRepository: Repository<AggregatedMetric>,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Execute all retention policies.
   *
   * This is the main entry point called by the cron job.
   * Processes all policies sequentially and returns results.
   */
  async executeAllPolicies(): Promise<CleanupResult[]> {
    this.logger.log('Starting data retention cleanup execution');
    const results: CleanupResult[] = [];

    try {
      // Process aggregated metrics
      results.push(await this.cleanupAggregatedMetrics());

      const totalDeleted = results.reduce(
        (sum, r) => sum + r.deletedCount,
        0,
      );
      const totalAnonymized = results.reduce(
        (sum, r) => sum + (r.anonymizedCount || 0),
        0,
      );

      this.logger.log(
        `Data retention cleanup completed: ${totalDeleted} deleted, ${totalAnonymized} anonymized`,
      );

      return results;
    } catch (error) {
      this.logger.error(
        `Data retention cleanup failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
        error instanceof Error ? error.stack : undefined,
      );
      throw error;
    }
  }

  /**
   * Clean up aggregated metrics older than retention period.
   */
  async cleanupAggregatedMetrics(): Promise<CleanupResult> {
    const policy = this.policies.aggregatedMetrics;
    const startTime = Date.now();
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - policy.retentionDays);

    this.logger.log(
      `Cleaning up aggregated metrics older than ${cutoffDate.toISOString()}`,
    );

    try {
      const deleteResult = await this.aggregatedMetricRepository.delete({
        timestamp: LessThan(cutoffDate),
      });

      const deletedCount =
        typeof deleteResult.affected === 'number' ? deleteResult.affected : 0;
      const durationMs = Date.now() - startTime;

      this.logAuditEntry({
        timestamp: new Date(),
        operation: 'cleanup',
        policy: policy.description,
        recordsAffected: deletedCount,
        cutoffDate,
        durationMs,
        success: true,
      });

      this.logger.log(
        `Cleaned up ${deletedCount} aggregated metrics in ${durationMs}ms`,
      );

      return {
        policy: policy.description,
        deletedCount,
        cutoffDate,
        durationMs,
      };
    } catch (error) {
      const durationMs = Date.now() - startTime;
      const errorMessage =
        error instanceof Error ? error.message : 'Unknown error';

      this.logAuditEntry({
        timestamp: new Date(),
        operation: 'cleanup',
        policy: policy.description,
        recordsAffected: 0,
        cutoffDate,
        durationMs,
        success: false,
        errorMessage,
      });

      this.logger.error(
        `Failed to cleanup aggregated metrics: ${errorMessage}`,
        error instanceof Error ? error.stack : undefined,
      );

      return {
        policy: policy.description,
        deletedCount: 0,
        cutoffDate,
        durationMs,
        errors: [errorMessage],
      };
    }
  }

  /**
   * Execute cleanup for a custom table.
   *
   * Allows platform-specific extensions to clean up their own tables
   * using the same infrastructure.
   */
  async cleanupCustomTable(
    tableName: string,
    timestampColumn: string,
    retentionDays: number,
    description: string,
  ): Promise<CleanupResult> {
    const startTime = Date.now();
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - retentionDays);

    this.logger.log(
      `Cleaning up ${tableName}: deleting records older than ${cutoffDate.toISOString()}`,
    );

    try {
      const deleteResult = await this.dataSource
        .createQueryBuilder()
        .delete()
        .from(tableName)
        .where(`${timestampColumn} < :cutoffDate`, { cutoffDate })
        .execute();

      const deletedCount =
        typeof deleteResult.affected === 'number' ? deleteResult.affected : 0;
      const durationMs = Date.now() - startTime;

      this.logAuditEntry({
        timestamp: new Date(),
        operation: 'cleanup',
        policy: description,
        recordsAffected: deletedCount,
        cutoffDate,
        durationMs,
        success: true,
      });

      this.logger.log(
        `Cleaned up ${deletedCount} records from ${tableName} in ${durationMs}ms`,
      );

      return {
        policy: description,
        deletedCount,
        cutoffDate,
        durationMs,
      };
    } catch (error) {
      const durationMs = Date.now() - startTime;
      const errorMessage =
        error instanceof Error ? error.message : 'Unknown error';

      this.logAuditEntry({
        timestamp: new Date(),
        operation: 'cleanup',
        policy: description,
        recordsAffected: 0,
        cutoffDate,
        durationMs,
        success: false,
        errorMessage,
      });

      return {
        policy: description,
        deletedCount: 0,
        cutoffDate,
        durationMs,
        errors: [errorMessage],
      };
    }
  }

  /**
   * Anonymize records in a custom table.
   *
   * Sets the subject column to NULL for records older than threshold
   * but younger than full retention period.
   */
  async anonymizeCustomTable(
    tableName: string,
    subjectColumn: string,
    timestampColumn: string,
    anonymizeAfterDays: number,
    fullRetentionDays: number,
    description: string,
  ): Promise<CleanupResult> {
    const startTime = Date.now();

    const anonymizeCutoff = new Date();
    anonymizeCutoff.setDate(anonymizeCutoff.getDate() - anonymizeAfterDays);

    const deleteCutoff = new Date();
    deleteCutoff.setDate(deleteCutoff.getDate() - fullRetentionDays);

    this.logger.log(
      `Anonymizing ${tableName} between ${deleteCutoff.toISOString()} and ${anonymizeCutoff.toISOString()}`,
    );

    try {
      const updateResult = await this.dataSource
        .createQueryBuilder()
        .update(tableName)
        .set({ [subjectColumn]: null })
        .where(`${timestampColumn} >= :deleteCutoff`, { deleteCutoff })
        .andWhere(`${timestampColumn} < :anonymizeCutoff`, { anonymizeCutoff })
        .andWhere(`${subjectColumn} IS NOT NULL`)
        .execute();

      const anonymizedCount =
        typeof updateResult.affected === 'number' ? updateResult.affected : 0;
      const durationMs = Date.now() - startTime;

      this.logAuditEntry({
        timestamp: new Date(),
        operation: 'anonymize',
        policy: description,
        recordsAffected: anonymizedCount,
        cutoffDate: anonymizeCutoff,
        durationMs,
        success: true,
      });

      this.logger.log(
        `Anonymized ${anonymizedCount} records in ${tableName} in ${durationMs}ms`,
      );

      return {
        policy: description,
        deletedCount: 0,
        anonymizedCount,
        cutoffDate: anonymizeCutoff,
        durationMs,
      };
    } catch (error) {
      const durationMs = Date.now() - startTime;
      const errorMessage =
        error instanceof Error ? error.message : 'Unknown error';

      this.logAuditEntry({
        timestamp: new Date(),
        operation: 'anonymize',
        policy: description,
        recordsAffected: 0,
        cutoffDate: anonymizeCutoff,
        durationMs,
        success: false,
        errorMessage,
      });

      return {
        policy: description,
        deletedCount: 0,
        anonymizedCount: 0,
        cutoffDate: anonymizeCutoff,
        durationMs,
        errors: [errorMessage],
      };
    }
  }

  /**
   * Get audit log of retention operations.
   */
  getAuditLog(): RetentionAuditLog[] {
    return [...this.auditLogs];
  }

  /**
   * Get summary of last execution.
   */
  getLastExecutionSummary(): {
    totalDeleted: number;
    totalAnonymized: number;
    operationCount: number;
    lastExecutionDate: Date | null;
  } {
    const recentLogs = this.auditLogs.filter(
      (log) => log.timestamp > new Date(Date.now() - 24 * 60 * 60 * 1000),
    );

    return {
      totalDeleted: recentLogs
        .filter((log) => log.operation === 'cleanup')
        .reduce((sum, log) => sum + log.recordsAffected, 0),
      totalAnonymized: recentLogs
        .filter((log) => log.operation === 'anonymize')
        .reduce((sum, log) => sum + log.recordsAffected, 0),
      operationCount: recentLogs.length,
      lastExecutionDate:
        recentLogs.length > 0 ? recentLogs[0].timestamp : null,
    };
  }

  /**
   * Log a retention audit entry.
   */
  private logAuditEntry(entry: RetentionAuditLog): void {
    this.auditLogs.push(entry);

    // Keep only last 1000 entries in memory
    if (this.auditLogs.length > 1000) {
      this.auditLogs.shift();
    }

    if (!entry.success) {
      this.logger.error(
        `Retention audit failure: ${entry.policy} - ${entry.errorMessage}`,
      );
    } else {
      this.logger.log(
        `Retention audit: ${entry.operation} ${entry.policy} - ${entry.recordsAffected} records`,
      );
    }
  }
}
