/**
 * TypeORM Audit Log Repository Implementation
 */

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuditLogRepository, AuditLogFilters } from '@domain/ports/out/audit-log.repository';
import { AuditLog, AuditStatus, AuditAction } from '@domain/entities/audit-log.entity';
import { AuditLogOrmEntity } from '../entities/audit-log.orm-entity';

@Injectable()
export class TypeOrmAuditLogRepository implements AuditLogRepository {
  constructor(
    @InjectRepository(AuditLogOrmEntity)
    private readonly ormRepository: Repository<AuditLogOrmEntity>
  ) {}

  async save(auditLog: AuditLog): Promise<void> {
    const ormEntity = this.toOrm(auditLog);
    await this.ormRepository.save(ormEntity);
  }

  async findById(id: string): Promise<AuditLog | null> {
    const ormEntity = await this.ormRepository.findOne({ where: { id } });
    return ormEntity ? this.toDomain(ormEntity) : null;
  }

  async findByFilters(filters: AuditLogFilters): Promise<AuditLog[]> {
    const query = this.ormRepository.createQueryBuilder('audit_log');

    if (filters.userId) {
      query.andWhere('audit_log.user_id = :userId', { userId: filters.userId });
    }

    if (filters.organizationId) {
      query.andWhere('audit_log.organization_id = :organizationId', {
        organizationId: filters.organizationId,
      });
    }

    if (filters.action && filters.action.length > 0) {
      query.andWhere('audit_log.action IN (:...actions)', { actions: filters.action });
    }

    if (filters.resourceType) {
      query.andWhere('audit_log.resource_type = :resourceType', {
        resourceType: filters.resourceType,
      });
    }

    if (filters.resourceId) {
      query.andWhere('audit_log.resource_id = :resourceId', {
        resourceId: filters.resourceId,
      });
    }

    if (filters.dateFrom) {
      query.andWhere('audit_log.timestamp >= :dateFrom', { dateFrom: filters.dateFrom });
    }

    if (filters.dateTo) {
      query.andWhere('audit_log.timestamp <= :dateTo', { dateTo: filters.dateTo });
    }

    query.orderBy('audit_log.timestamp', 'DESC');

    if (filters.limit) {
      query.limit(filters.limit);
    }

    if (filters.offset) {
      query.offset(filters.offset);
    }

    const ormEntities = await query.getMany();
    return ormEntities.map(e => this.toDomain(e));
  }

  async count(filters: AuditLogFilters): Promise<number> {
    const query = this.ormRepository.createQueryBuilder('audit_log');

    if (filters.userId) {
      query.andWhere('audit_log.user_id = :userId', { userId: filters.userId });
    }

    if (filters.organizationId) {
      query.andWhere('audit_log.organization_id = :organizationId', {
        organizationId: filters.organizationId,
      });
    }

    if (filters.action && filters.action.length > 0) {
      query.andWhere('audit_log.action IN (:...actions)', { actions: filters.action });
    }

    if (filters.resourceType) {
      query.andWhere('audit_log.resource_type = :resourceType', {
        resourceType: filters.resourceType,
      });
    }

    if (filters.resourceId) {
      query.andWhere('audit_log.resource_id = :resourceId', {
        resourceId: filters.resourceId,
      });
    }

    if (filters.dateFrom) {
      query.andWhere('audit_log.timestamp >= :dateFrom', { dateFrom: filters.dateFrom });
    }

    if (filters.dateTo) {
      query.andWhere('audit_log.timestamp <= :dateTo', { dateTo: filters.dateTo });
    }

    return query.getCount();
  }

  async findByResource(resourceType: string, resourceId: string): Promise<AuditLog[]> {
    const ormEntities = await this.ormRepository.find({
      where: {
        resource_type: resourceType,
        resource_id: resourceId,
      },
      order: {
        timestamp: 'DESC',
      },
    });

    return ormEntities.map(e => this.toDomain(e));
  }

  async findRecentByOrganization(organizationId: string, limit: number): Promise<AuditLog[]> {
    const ormEntities = await this.ormRepository.find({
      where: {
        organization_id: organizationId,
      },
      order: {
        timestamp: 'DESC',
      },
      take: limit,
    });

    return ormEntities.map(e => this.toDomain(e));
  }

  async findByUser(userId: string, limit: number): Promise<AuditLog[]> {
    const ormEntities = await this.ormRepository.find({
      where: {
        user_id: userId,
      },
      order: {
        timestamp: 'DESC',
      },
      take: limit,
    });

    return ormEntities.map(e => this.toDomain(e));
  }

  /**
   * Map ORM entity to domain entity
   */
  private toDomain(ormEntity: AuditLogOrmEntity): AuditLog {
    return AuditLog.fromPersistence({
      id: ormEntity.id,
      action: ormEntity.action as AuditAction,
      status: ormEntity.status as AuditStatus,
      userId: ormEntity.user_id,
      userEmail: ormEntity.user_email,
      organizationId: ormEntity.organization_id,
      resourceType: ormEntity.resource_type,
      resourceId: ormEntity.resource_id,
      resourceName: ormEntity.resource_name,
      metadata: ormEntity.metadata,
      ipAddress: ormEntity.ip_address,
      userAgent: ormEntity.user_agent,
      errorMessage: ormEntity.error_message,
      timestamp: ormEntity.timestamp,
    });
  }

  /**
   * Map domain entity to ORM entity
   */
  private toOrm(auditLog: AuditLog): AuditLogOrmEntity {
    const ormEntity = new AuditLogOrmEntity();
    ormEntity.id = auditLog.id;
    ormEntity.action = auditLog.action;
    ormEntity.status = auditLog.status;
    ormEntity.user_id = auditLog.userId;
    ormEntity.user_email = auditLog.userEmail;
    ormEntity.organization_id = auditLog.organizationId;
    ormEntity.resource_type = auditLog.resourceType;
    ormEntity.resource_id = auditLog.resourceId;
    ormEntity.resource_name = auditLog.resourceName;
    ormEntity.metadata = auditLog.metadata;
    ormEntity.ip_address = auditLog.ipAddress;
    ormEntity.user_agent = auditLog.userAgent;
    ormEntity.error_message = auditLog.errorMessage;
    ormEntity.timestamp = auditLog.timestamp;
    return ormEntity;
  }
}
