/**
 * EntityChange Service
 * Data modification versioning and change history tracking.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import { EntityChange, ChangeType } from '../entities/entity-change.entity';
import { ServiceContext, PaginatedResult } from './audit-log.service';

export interface CreateEntityChangeDto {
  entityType: string;
  entityId: string;
  entityName?: string;
  dataSnapshot: Record<string, any>;
  changes?: Record<string, any>[];
  changedBy?: string;
  changeReason?: string;
  changeType: ChangeType;
}

export interface EntityChangeFilters {
  entityType?: string;
  entityId?: string;
  changedBy?: string;
  changeType?: ChangeType;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface EntityChangeComparison {
  version1: EntityChange;
  version2: EntityChange;
  differences: {
    field: string;
    oldValue: any;
    newValue: any;
  }[];
}

export class EntityChangeService {
  constructor(private readonly repository: Repository<EntityChange>) {}

  /**
   * Record a new entity change
   */
  async create(
    ctx: ServiceContext,
    dto: CreateEntityChangeDto,
  ): Promise<EntityChange> {
    // Get the latest version for this entity
    const latestChange = await this.repository
      .createQueryBuilder('ec')
      .where('ec.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ec.entity_type = :entityType', { entityType: dto.entityType })
      .andWhere('ec.entity_id = :entityId', { entityId: dto.entityId })
      .orderBy('ec.version', 'DESC')
      .getOne();

    const version = latestChange ? latestChange.version + 1 : 1;
    const previousVersion = latestChange ? latestChange.version : undefined;

    const change = this.repository.create({
      tenantId: ctx.tenantId,
      entityType: dto.entityType,
      entityId: dto.entityId,
      entityName: dto.entityName,
      version,
      previousVersion,
      dataSnapshot: dto.dataSnapshot,
      changes: dto.changes || [],
      changedBy: dto.changedBy || ctx.userId,
      changeReason: dto.changeReason,
      changeType: dto.changeType,
    } as Partial<EntityChange>);

    return this.repository.save(change) as Promise<EntityChange>;
  }

  /**
   * Get change history for an entity
   */
  async getHistory(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
    page = 1,
    limit = 20,
  ): Promise<PaginatedResult<EntityChange>> {
    const skip = (page - 1) * limit;

    const qb = this.repository
      .createQueryBuilder('ec')
      .where('ec.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ec.entity_type = :entityType', { entityType })
      .andWhere('ec.entity_id = :entityId', { entityId })
      .orderBy('ec.version', 'DESC')
      .skip(skip)
      .take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get a specific version of an entity
   */
  async getVersion(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
    version: number,
  ): Promise<EntityChange | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        entityType,
        entityId,
        version,
      },
    });
  }

  /**
   * Get the latest version of an entity
   */
  async getLatestVersion(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
  ): Promise<EntityChange | null> {
    return this.repository
      .createQueryBuilder('ec')
      .where('ec.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ec.entity_type = :entityType', { entityType })
      .andWhere('ec.entity_id = :entityId', { entityId })
      .orderBy('ec.version', 'DESC')
      .getOne();
  }

  /**
   * Compare two versions of an entity
   */
  async compareVersions(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
    version1: number,
    version2: number,
  ): Promise<EntityChangeComparison | null> {
    const [v1, v2] = await Promise.all([
      this.getVersion(ctx, entityType, entityId, version1),
      this.getVersion(ctx, entityType, entityId, version2),
    ]);

    if (!v1 || !v2) {
      return null;
    }

    const differences: { field: string; oldValue: any; newValue: any }[] = [];
    const allKeys = new Set([
      ...Object.keys(v1.dataSnapshot),
      ...Object.keys(v2.dataSnapshot),
    ]);

    for (const key of allKeys) {
      const oldValue = v1.dataSnapshot[key];
      const newValue = v2.dataSnapshot[key];

      if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
        differences.push({
          field: key,
          oldValue,
          newValue,
        });
      }
    }

    return {
      version1: v1,
      version2: v2,
      differences,
    };
  }

  /**
   * Find changes with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: EntityChangeFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<EntityChange>> {
    const qb = this.repository
      .createQueryBuilder('ec')
      .where('ec.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.entityType) {
      qb.andWhere('ec.entity_type = :entityType', { entityType: filters.entityType });
    }
    if (filters.entityId) {
      qb.andWhere('ec.entity_id = :entityId', { entityId: filters.entityId });
    }
    if (filters.changedBy) {
      qb.andWhere('ec.changed_by = :changedBy', { changedBy: filters.changedBy });
    }
    if (filters.changeType) {
      qb.andWhere('ec.change_type = :changeType', { changeType: filters.changeType });
    }
    if (filters.dateFrom) {
      qb.andWhere('ec.changed_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('ec.changed_at <= :dateTo', { dateTo: filters.dateTo });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('ec.changed_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Get changes by user
   */
  async findByUser(
    ctx: ServiceContext,
    userId: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<EntityChange>> {
    return this.findWithFilters(ctx, { changedBy: userId }, page, limit);
  }

  /**
   * Get recent changes
   */
  async getRecentChanges(
    ctx: ServiceContext,
    days = 7,
    limit = 100,
  ): Promise<EntityChange[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    return this.repository
      .createQueryBuilder('ec')
      .where('ec.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ec.changed_at >= :dateFrom', { dateFrom })
      .orderBy('ec.changed_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get version count for an entity
   */
  async getVersionCount(
    ctx: ServiceContext,
    entityType: string,
    entityId: string,
  ): Promise<number> {
    return this.repository
      .createQueryBuilder('ec')
      .where('ec.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ec.entity_type = :entityType', { entityType })
      .andWhere('ec.entity_id = :entityId', { entityId })
      .getCount();
  }
}
