import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuditLogORM } from '../../infrastructure/database/postgres/entities/audit-log.orm';

export type ActorType = 'USER' | 'ADMIN' | 'SYSTEM';
export type AuditResult = 'SUCCESS' | 'FAILED';

export interface CreateAuditLogParams {
  actorId?: string;
  actorType: ActorType;
  actorName?: string;
  action: string;
  entityType: string;
  entityId?: string;
  oldValues?: Record<string, unknown>;
  newValues?: Record<string, unknown>;
  changedFields?: string[];
  description?: string;
  ipAddress?: string;
  userAgent?: string;
  requestId?: string;
  result: AuditResult;
  errorMessage?: string;
}

export interface AuditLogDto {
  id: string;
  actorId: string | null;
  actorType: string;
  actorName: string | null;
  action: string;
  entityType: string;
  entityId: string | null;
  oldValues: Record<string, unknown> | null;
  newValues: Record<string, unknown> | null;
  changedFields: string[] | null;
  description: string | null;
  ipAddress: string | null;
  userAgent: string | null;
  requestId: string | null;
  result: string;
  errorMessage: string | null;
  createdAt: Date;
}

export interface AuditLogQueryParams {
  actorType?: ActorType;
  actorId?: string;
  action?: string;
  entityType?: string;
  entityId?: string;
  result?: AuditResult;
  startDate?: string;
  endDate?: string;
  page?: number;
  pageSize?: number;
}

export interface PaginatedAuditLogs {
  data: AuditLogDto[];
  total: number;
  page: number;
  pageSize: number;
  totalPages: number;
}

@Injectable()
export class AuditLogService {
  private readonly logger = new Logger(AuditLogService.name);

  constructor(
    @InjectRepository(AuditLogORM)
    private readonly auditRepo: Repository<AuditLogORM>,
  ) {}

  /**
   * Log an audit event
   */
  async log(params: CreateAuditLogParams): Promise<void> {
    try {
      const audit = this.auditRepo.create({
        actorId: params.actorId || null,
        actorType: params.actorType,
        actorName: params.actorName || null,
        action: params.action,
        entityType: params.entityType,
        entityId: params.entityId || null,
        oldValues: params.oldValues || null,
        newValues: params.newValues || null,
        changedFields: params.changedFields || null,
        description: params.description || null,
        ipAddress: params.ipAddress || null,
        userAgent: params.userAgent || null,
        requestId: params.requestId || null,
        result: params.result,
        errorMessage: params.errorMessage || null,
      });

      await this.auditRepo.save(audit);
    } catch (error) {
      // Don't throw - audit logging should not break the main flow
      this.logger.error('Failed to write audit log:', error);
    }
  }

  /**
   * Query audit logs with filters
   */
  async queryLogs(params: AuditLogQueryParams): Promise<PaginatedAuditLogs> {
    const page = params.page || 1;
    const pageSize = params.pageSize || 50;
    const skip = (page - 1) * pageSize;

    const query = this.auditRepo.createQueryBuilder('log');

    if (params.actorType) {
      query.andWhere('log.actorType = :actorType', { actorType: params.actorType });
    }

    if (params.actorId) {
      query.andWhere('log.actorId = :actorId', { actorId: params.actorId });
    }

    if (params.action) {
      query.andWhere('log.action = :action', { action: params.action });
    }

    if (params.entityType) {
      query.andWhere('log.entityType = :entityType', { entityType: params.entityType });
    }

    if (params.entityId) {
      query.andWhere('log.entityId = :entityId', { entityId: params.entityId });
    }

    if (params.result) {
      query.andWhere('log.result = :result', { result: params.result });
    }

    if (params.startDate) {
      query.andWhere('log.createdAt >= :startDate', { startDate: params.startDate });
    }

    if (params.endDate) {
      query.andWhere('log.createdAt <= :endDate', { endDate: params.endDate });
    }

    const [logs, total] = await query
      .orderBy('log.createdAt', 'DESC')
      .skip(skip)
      .take(pageSize)
      .getManyAndCount();

    return {
      data: logs.map(this.toDto),
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  /**
   * Get a specific audit log
   */
  async getLog(id: string): Promise<AuditLogDto | null> {
    const log = await this.auditRepo.findOne({ where: { id } });
    return log ? this.toDto(log) : null;
  }

  /**
   * Get audit history for an entity
   */
  async getEntityHistory(entityType: string, entityId: string): Promise<AuditLogDto[]> {
    const logs = await this.auditRepo.find({
      where: { entityType, entityId },
      order: { createdAt: 'DESC' },
    });

    return logs.map(this.toDto);
  }

  /**
   * Get audit history for an actor
   */
  async getActorHistory(actorId: string, page = 1, pageSize = 50): Promise<PaginatedAuditLogs> {
    return this.queryLogs({ actorId, page, pageSize });
  }

  /**
   * Log a system event
   */
  async logSystem(
    action: string,
    entityType: string,
    entityId?: string,
    description?: string,
  ): Promise<void> {
    await this.log({
      actorType: 'SYSTEM',
      actorName: 'System',
      action,
      entityType,
      entityId,
      description,
      result: 'SUCCESS',
    });
  }

  private toDto(orm: AuditLogORM): AuditLogDto {
    return {
      id: orm.id,
      actorId: orm.actorId,
      actorType: orm.actorType,
      actorName: orm.actorName,
      action: orm.action,
      entityType: orm.entityType,
      entityId: orm.entityId,
      oldValues: orm.oldValues,
      newValues: orm.newValues,
      changedFields: orm.changedFields,
      description: orm.description,
      ipAddress: orm.ipAddress,
      userAgent: orm.userAgent,
      requestId: orm.requestId,
      result: orm.result,
      errorMessage: orm.errorMessage,
      createdAt: orm.createdAt,
    };
  }
}
