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

import {
  DeletionAuditLog,
  DeletionStatus,
  type TableDeletionResult,
} from '../entities/deletion-audit-log.entity';
import { AggregatedMetric } from '../entities/aggregated-metric.entity';

/**
 * GDPR deletion request options
 */
export interface DeletionRequestOptions {
  /** Who initiated the deletion (user ID or admin ID) */
  requestedBy?: string;
  /** Source of the deletion request */
  requestSource?: 'user' | 'admin' | 'automated';
  /** Additional notes for audit trail */
  notes?: string;
}

/**
 * Deletion verification result
 */
export interface DeletionVerificationResult {
  verified: boolean;
  recordsFound: number;
  tables: Record<string, number>;
}

/**
 * User analytics data export structure
 */
export interface UserAnalyticsExport {
  subjectId: string;
  exportedAt: Date;
  metrics: Array<{
    id: string;
    metricType: string;
    granularity: string;
    timestamp: Date;
    value: number;
    count: number;
    dimension?: string;
    dimensionValue?: string;
  }>;
  statistics: {
    totalMetrics: number;
    firstSeen: Date | null;
    lastSeen: Date | null;
  };
}

/**
 * GdprService - GDPR compliance implementation
 *
 * Handles:
 * - Article 15: Right of access (data export)
 * - Article 17: Right to erasure (deletion)
 * - Article 20: Right to data portability
 *
 * Design:
 * - Sync deletion for this generic service (platform-specific can use async queues)
 * - Comprehensive audit logging
 * - Verification step to confirm complete erasure
 */
@Injectable()
export class GdprService {
  private readonly logger = new Logger(GdprService.name);

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

  /**
   * Request and execute deletion of all user analytics data
   *
   * @param subjectId - Data subject ID to delete data for
   * @param options - Deletion request options
   * @returns Audit log entry tracking the deletion
   */
  async requestDeletion(
    subjectId: string,
    options: DeletionRequestOptions = {},
  ): Promise<DeletionAuditLog> {
    this.logger.log(`GDPR deletion requested for subject ${subjectId}`);

    // Create audit log entry
    const auditLog = this.auditLogRepo.create({
      subjectId,
      status: DeletionStatus.PENDING,
      requestedAt: new Date(),
      metadata: {
        requestedBy: options.requestedBy || subjectId,
        requestSource: options.requestSource || 'user',
        gdprArticle: 'Article 17',
        notes: options.notes,
      },
    });

    await this.auditLogRepo.save(auditLog);

    // Execute deletion synchronously for this generic service
    try {
      return await this.processDeletion(subjectId, auditLog.id);
    } catch (error) {
      this.logger.error(
        `GDPR deletion failed for subject ${subjectId}:`,
        error instanceof Error ? error.message : 'Unknown error',
      );
      throw error;
    }
  }

  /**
   * Process deletion of user data
   */
  async processDeletion(subjectId: string, auditLogId: string): Promise<DeletionAuditLog> {
    const startTime = Date.now();

    const auditLog = await this.auditLogRepo.findOneBy({ id: auditLogId });
    if (!auditLog) {
      throw new NotFoundException(`Audit log ${auditLogId} not found`);
    }

    auditLog.status = DeletionStatus.PROCESSING;
    auditLog.startedAt = new Date();
    await this.auditLogRepo.save(auditLog);

    this.logger.log(`Processing GDPR deletion for subject ${subjectId}`);

    const tableResults: TableDeletionResult[] = [];
    let totalDeleted = 0;

    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // Delete from aggregated_metrics where dimension is subject-related
      // (This generic service has limited data - platform-specific implementations extend this)
      const metricsStart = Date.now();
      const metricsDeleted = await queryRunner.manager
        .createQueryBuilder()
        .delete()
        .from(AggregatedMetric)
        .where("dimension = 'subject_id' AND dimensionValue = :subjectId", { subjectId })
        .execute();

      tableResults.push({
        table: 'aggregated_metrics',
        status: 'success',
        recordsDeleted: metricsDeleted.affected || 0,
        startedAt: new Date(metricsStart).toISOString(),
        completedAt: new Date().toISOString(),
        durationMs: Date.now() - metricsStart,
      });
      totalDeleted += metricsDeleted.affected || 0;

      await queryRunner.commitTransaction();

      // Update audit log with success
      const durationMs = Date.now() - startTime;
      auditLog.status = DeletionStatus.COMPLETED;
      auditLog.completedAt = new Date();
      auditLog.durationMs = durationMs;
      auditLog.tableResults = tableResults;
      auditLog.totalRecordsDeleted = totalDeleted;
      auditLog.totalRecordsAnonymized = 0;
      await this.auditLogRepo.save(auditLog);

      this.logger.log(
        `GDPR deletion completed for subject ${subjectId}: ${totalDeleted} deleted (${durationMs}ms)`,
      );

      return auditLog;
    } catch (error) {
      await queryRunner.rollbackTransaction();

      auditLog.status = DeletionStatus.FAILED;
      auditLog.completedAt = new Date();
      auditLog.durationMs = Date.now() - startTime;
      auditLog.tableResults = tableResults;
      auditLog.totalRecordsDeleted = totalDeleted;
      auditLog.errorDetails = {
        message: error instanceof Error ? error.message : 'Unknown error',
        stack: error instanceof Error ? error.stack : undefined,
        partialResults: tableResults,
      };
      await this.auditLogRepo.save(auditLog);

      throw error;
    } finally {
      await queryRunner.release();
    }
  }

  /**
   * Verify that all user data has been deleted
   */
  async verifyDeletion(subjectId: string): Promise<DeletionVerificationResult> {
    this.logger.log(`Verifying GDPR deletion for subject ${subjectId}`);

    const tables: Record<string, number> = {};

    // Check aggregated metrics
    const metricsCount = await this.aggregatedMetricRepo
      .createQueryBuilder('m')
      .where("m.dimension = 'subject_id' AND m.dimensionValue = :subjectId", { subjectId })
      .getCount();
    tables['aggregated_metrics'] = metricsCount;

    const totalRecordsFound = Object.values(tables).reduce((sum, count) => sum + count, 0);
    const verified = totalRecordsFound === 0;

    this.logger.log(
      `GDPR deletion verification for subject ${subjectId}: ${verified ? 'VERIFIED' : 'FAILED'} (${totalRecordsFound} records found)`,
    );

    return {
      verified,
      recordsFound: totalRecordsFound,
      tables,
    };
  }

  /**
   * Export all analytics data for a given subject
   */
  async exportUserData(subjectId: string): Promise<UserAnalyticsExport> {
    this.logger.log(`Data export requested for subject ${subjectId}`);

    // Find all metrics with this subject
    const metrics = await this.aggregatedMetricRepo
      .createQueryBuilder('m')
      .where("m.dimension = 'subject_id' AND m.dimensionValue = :subjectId", { subjectId })
      .orderBy('m.timestamp', 'DESC')
      .getMany();

    // Calculate statistics
    const timestamps = metrics.map((m) => m.timestamp.getTime());
    const firstSeen = timestamps.length > 0 ? new Date(Math.min(...timestamps)) : null;
    const lastSeen = timestamps.length > 0 ? new Date(Math.max(...timestamps)) : null;

    return {
      subjectId,
      exportedAt: new Date(),
      metrics: metrics.map((m) => ({
        id: m.id,
        metricType: m.metricType,
        granularity: m.granularity,
        timestamp: m.timestamp,
        value: m.value,
        count: Number(m.count),
        dimension: m.dimension,
        dimensionValue: m.dimensionValue,
      })),
      statistics: {
        totalMetrics: metrics.length,
        firstSeen,
        lastSeen,
      },
    };
  }

  /**
   * Get deletion status for a subject
   */
  async getDeletionStatus(subjectId: string): Promise<DeletionAuditLog | null> {
    return this.auditLogRepo.findOne({
      where: { subjectId },
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Get all deletion audit logs (admin only)
   */
  async getAllDeletionLogs(limit = 100, offset = 0): Promise<DeletionAuditLog[]> {
    return this.auditLogRepo.find({
      order: { createdAt: 'DESC' },
      take: limit,
      skip: offset,
    });
  }

  /**
   * Format export data as JSON
   */
  formatAsJson(data: UserAnalyticsExport): string {
    return JSON.stringify(data, null, 2);
  }

  /**
   * Format export data as human-readable text
   */
  formatAsReadable(data: UserAnalyticsExport): string {
    const lines: string[] = [];

    lines.push('='.repeat(80));
    lines.push('PERSONAL DATA EXPORT - ANALYTICS');
    lines.push('='.repeat(80));
    lines.push('');
    lines.push(`Subject ID: ${data.subjectId}`);
    lines.push(`Export Date: ${data.exportedAt.toISOString()}`);
    lines.push('');

    lines.push('OVERVIEW STATISTICS');
    lines.push('-'.repeat(80));
    lines.push(`Total Metrics: ${data.statistics.totalMetrics}`);
    lines.push(`First Activity: ${data.statistics.firstSeen?.toISOString() ?? 'N/A'}`);
    lines.push(`Last Activity: ${data.statistics.lastSeen?.toISOString() ?? 'N/A'}`);
    lines.push('');

    if (data.metrics.length > 0) {
      lines.push('METRICS');
      lines.push('-'.repeat(80));
      data.metrics.slice(0, 20).forEach((metric, index) => {
        lines.push(`Metric ${index + 1}:`);
        lines.push(`  Type: ${metric.metricType}`);
        lines.push(`  Granularity: ${metric.granularity}`);
        lines.push(`  Timestamp: ${metric.timestamp.toISOString()}`);
        lines.push(`  Value: ${metric.value}`);
        lines.push('');
      });
      if (data.metrics.length > 20) {
        lines.push(`... and ${data.metrics.length - 20} more metrics (see JSON export for full data)`);
        lines.push('');
      }
    }

    lines.push('='.repeat(80));
    lines.push('END OF EXPORT');
    lines.push('='.repeat(80));

    return lines.join('\n');
  }
}
