import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThan, MoreThan } from 'typeorm';
import { TenantContextService } from '@iconsulting/shared';
import {
  IUserMemoryRepository,
  ISystemExperienceRepository,
} from '../../../domain/repositories/memory.repository.interface';
import { UserMemoryEntity, MemoryType } from '../../../domain/entities/user-memory.entity';
import {
  SystemExperienceEntity,
  ExperienceType,
  VerificationStatus,
} from '../../../domain/entities/system-experience.entity';
import { UserMemoryORM } from '../../../infrastructure/database/postgres/entities/user-memory.orm';
import { SystemExperienceORM } from '../../../infrastructure/database/postgres/entities/system-experience.orm';

@Injectable()
export class UserMemoryPostgresRepository implements IUserMemoryRepository {
  constructor(
    @InjectRepository(UserMemoryORM)
    private memoryRepo: Repository<UserMemoryORM>,
    private readonly tenantContext: TenantContextService,
  ) {}

  private getTenantId(): string {
    const id = this.tenantContext.getCurrentTenantId();
    if (!id) throw new Error('Tenant context not set');
    return id;
  }

  async save(memory: UserMemoryEntity): Promise<void> {
    const orm = this.toORM(memory);
    orm.tenantId = this.getTenantId();
    await this.memoryRepo.save(orm);
  }

  async findById(id: string): Promise<UserMemoryEntity | null> {
    const orm = await this.memoryRepo.findOne({
      where: { id, tenantId: this.getTenantId() },
    });
    return orm ? this.toEntity(orm) : null;
  }

  async findByUserId(
    userId: string,
    options?: { memoryType?: MemoryType; includeExpired?: boolean; limit?: number },
  ): Promise<UserMemoryEntity[]> {
    const query = this.memoryRepo.createQueryBuilder('memory')
      .where('memory.tenant_id = :tenantId', { tenantId: this.getTenantId() })
      .andWhere('memory.userId = :userId', { userId });

    if (options?.memoryType) {
      query.andWhere('memory.memoryType = :type', { type: options.memoryType });
    }

    if (!options?.includeExpired) {
      query.andWhere('memory.isExpired = false');
    }

    query.orderBy('memory.importance', 'DESC')
      .addOrderBy('memory.createdAt', 'DESC');

    if (options?.limit) {
      query.take(options.limit);
    }

    const orms = await query.getMany();
    return orms.map(orm => this.toEntity(orm));
  }

  async searchByVector(
    userId: string,
    embedding: number[],
    options?: { memoryType?: MemoryType; limit?: number; minSimilarity?: number },
  ): Promise<Array<{ memory: UserMemoryEntity; similarity: number }>> {
    const tenantId = this.getTenantId();
    const embeddingStr = `[${embedding.join(',')}]`;
    const limit = options?.limit || 5;
    const minSimilarity = options?.minSimilarity || 0.7;

    let sql = `
      SELECT *,
        1 - (embedding <=> '${embeddingStr}'::vector) as similarity
      FROM user_memories
      WHERE tenant_id = $1
        AND user_id = $2
        AND embedding IS NOT NULL
        AND is_expired = false
    `;

    if (options?.memoryType) {
      sql += ` AND memory_type = '${options.memoryType}'`;
    }

    sql += `
        AND 1 - (embedding <=> '${embeddingStr}'::vector) >= ${minSimilarity}
      ORDER BY similarity DESC
      LIMIT ${limit}
    `;

    const results = await this.memoryRepo.query(sql, [tenantId, userId]);

    return results.map((row: any) => ({
      memory: this.toEntityFromRaw(row),
      similarity: parseFloat(row.similarity),
    }));
  }

  async findTopMemories(userId: string, limit: number): Promise<UserMemoryEntity[]> {
    const orms = await this.memoryRepo.find({
      where: { userId, tenantId: this.getTenantId(), isExpired: false },
      order: { importance: 'DESC', accessCount: 'DESC' },
      take: limit,
    });
    return orms.map(orm => this.toEntity(orm));
  }

  async update(memory: UserMemoryEntity): Promise<void> {
    const orm = this.toORM(memory);
    orm.tenantId = this.getTenantId();
    await this.memoryRepo.save(orm);
  }

  async delete(id: string): Promise<void> {
    await this.memoryRepo.delete({ id, tenantId: this.getTenantId() });
  }

  async deleteByUserId(userId: string): Promise<void> {
    await this.memoryRepo.delete({ userId, tenantId: this.getTenantId() });
  }

  async markExpiredMemories(userId: string, olderThanDays: number): Promise<number> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);

    const result = await this.memoryRepo.update(
      {
        userId,
        tenantId: this.getTenantId(),
        isExpired: false,
        updatedAt: LessThan(cutoffDate),
      },
      { isExpired: true },
    );

    return result.affected || 0;
  }

  private toORM(entity: UserMemoryEntity): UserMemoryORM {
    const orm = new UserMemoryORM();
    orm.id = entity.id;
    orm.tenantId = this.getTenantId();
    orm.userId = entity.userId;
    orm.memoryType = entity.memoryType;
    orm.content = entity.content;
    orm.importance = entity.importance;
    orm.sourceConversationId = entity.sourceConversationId;
    orm.relatedCategory = entity.relatedCategory;
    orm.embedding = entity.embedding;
    orm.accessCount = entity.accessCount;
    orm.lastAccessedAt = entity.lastAccessedAt;
    orm.isExpired = entity.isExpired;
    orm.createdAt = entity.createdAt;
    orm.updatedAt = entity.updatedAt;
    return orm;
  }

  private toEntity(orm: UserMemoryORM): UserMemoryEntity {
    return UserMemoryEntity.fromPersistence({
      id: orm.id,
      userId: orm.userId,
      memoryType: orm.memoryType as MemoryType,
      content: orm.content,
      importance: orm.importance,
      sourceConversationId: orm.sourceConversationId,
      relatedCategory: orm.relatedCategory,
      embedding: orm.embedding,
      accessCount: orm.accessCount,
      lastAccessedAt: orm.lastAccessedAt,
      isExpired: orm.isExpired,
      createdAt: orm.createdAt,
      updatedAt: orm.updatedAt,
    });
  }

  private toEntityFromRaw(row: any): UserMemoryEntity {
    return UserMemoryEntity.fromPersistence({
      id: row.id,
      userId: row.user_id,
      memoryType: row.memory_type as MemoryType,
      content: row.content,
      importance: row.importance,
      sourceConversationId: row.source_conversation_id,
      relatedCategory: row.related_category,
      embedding: row.embedding,
      accessCount: row.access_count,
      lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
      isExpired: row.is_expired,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at),
    });
  }
}

@Injectable()
export class SystemExperiencePostgresRepository implements ISystemExperienceRepository {
  constructor(
    @InjectRepository(SystemExperienceORM)
    private experienceRepo: Repository<SystemExperienceORM>,
    private readonly tenantContext: TenantContextService,
  ) {}

  private getTenantId(): string {
    const id = this.tenantContext.getCurrentTenantId();
    if (!id) throw new Error('Tenant context not set');
    return id;
  }

  async save(experience: SystemExperienceEntity): Promise<void> {
    const orm = this.toORM(experience);
    orm.tenantId = this.getTenantId();
    await this.experienceRepo.save(orm);
  }

  async findById(id: string): Promise<SystemExperienceEntity | null> {
    const orm = await this.experienceRepo.findOne({
      where: { id, tenantId: this.getTenantId() },
    });
    return orm ? this.toEntity(orm) : null;
  }

  async findPendingExperiences(options?: {
    experienceType?: ExperienceType;
    limit?: number;
    offset?: number;
  }): Promise<SystemExperienceEntity[]> {
    const query = this.experienceRepo.createQueryBuilder('exp')
      .where('exp.tenant_id = :tenantId', { tenantId: this.getTenantId() })
      .andWhere('exp.verificationStatus = :status', { status: VerificationStatus.PENDING });

    if (options?.experienceType) {
      query.andWhere('exp.experienceType = :type', { type: options.experienceType });
    }

    query.orderBy('exp.confidence', 'DESC')
      .addOrderBy('exp.createdAt', 'DESC');

    if (options?.limit) query.take(options.limit);
    if (options?.offset) query.skip(options.offset);

    const orms = await query.getMany();
    return orms.map(orm => this.toEntity(orm));
  }

  async findActiveExperiences(options?: {
    experienceType?: ExperienceType;
    category?: string;
    minConfidence?: number;
    limit?: number;
  }): Promise<SystemExperienceEntity[]> {
    const query = this.experienceRepo.createQueryBuilder('exp')
      .where('exp.tenant_id = :tenantId', { tenantId: this.getTenantId() })
      .andWhere('exp.isActive = true');

    if (options?.experienceType) {
      query.andWhere('exp.experienceType = :type', { type: options.experienceType });
    }

    if (options?.category) {
      query.andWhere('exp.relatedCategory = :category', { category: options.category });
    }

    if (options?.minConfidence) {
      query.andWhere('exp.confidence >= :minConf', { minConf: options.minConfidence });
    }

    query.orderBy('exp.confidence', 'DESC');

    if (options?.limit) query.take(options.limit);

    const orms = await query.getMany();
    return orms.map(orm => this.toEntity(orm));
  }

  async searchByVector(
    embedding: number[],
    options?: {
      experienceType?: ExperienceType;
      activeOnly?: boolean;
      limit?: number;
      minSimilarity?: number;
    },
  ): Promise<Array<{ experience: SystemExperienceEntity; similarity: number }>> {
    const tenantId = this.getTenantId();
    const embeddingStr = `[${embedding.join(',')}]`;
    const limit = options?.limit || 5;
    const minSimilarity = options?.minSimilarity || 0.7;

    let sql = `
      SELECT *,
        1 - (embedding <=> '${embeddingStr}'::vector) as similarity
      FROM system_experiences
      WHERE tenant_id = $1
        AND embedding IS NOT NULL
    `;

    if (options?.activeOnly !== false) {
      sql += ` AND is_active = true`;
    }

    if (options?.experienceType) {
      sql += ` AND experience_type = '${options.experienceType}'`;
    }

    sql += `
        AND 1 - (embedding <=> '${embeddingStr}'::vector) >= ${minSimilarity}
      ORDER BY similarity DESC
      LIMIT ${limit}
    `;

    const results = await this.experienceRepo.query(sql, [tenantId]);

    return results.map((row: any) => ({
      experience: this.toEntityFromRaw(row),
      similarity: parseFloat(row.similarity),
    }));
  }

  async findSimilarExperiences(
    embedding: number[],
    threshold: number,
  ): Promise<SystemExperienceEntity[]> {
    const tenantId = this.getTenantId();
    const embeddingStr = `[${embedding.join(',')}]`;

    const sql = `
      SELECT *
      FROM system_experiences
      WHERE tenant_id = $1
        AND embedding IS NOT NULL
        AND 1 - (embedding <=> '${embeddingStr}'::vector) >= ${threshold}
      ORDER BY 1 - (embedding <=> '${embeddingStr}'::vector) DESC
      LIMIT 10
    `;

    const results = await this.experienceRepo.query(sql, [tenantId]);
    return results.map((row: any) => this.toEntityFromRaw(row));
  }

  async update(experience: SystemExperienceEntity): Promise<void> {
    const orm = this.toORM(experience);
    orm.tenantId = this.getTenantId();
    await this.experienceRepo.save(orm);
  }

  async delete(id: string): Promise<void> {
    await this.experienceRepo.delete({ id, tenantId: this.getTenantId() });
  }

  async getStatistics(): Promise<{
    total: number;
    byStatus: Record<VerificationStatus, number>;
    byType: Record<ExperienceType, number>;
  }> {
    const tenantId = this.getTenantId();

    const total = await this.experienceRepo.count({
      where: { tenantId },
    });

    const statusCounts = await this.experienceRepo
      .createQueryBuilder('exp')
      .select('exp.verificationStatus', 'status')
      .addSelect('COUNT(*)', 'count')
      .where('exp.tenant_id = :tenantId', { tenantId })
      .groupBy('exp.verificationStatus')
      .getRawMany();

    const typeCounts = await this.experienceRepo
      .createQueryBuilder('exp')
      .select('exp.experienceType', 'type')
      .addSelect('COUNT(*)', 'count')
      .where('exp.tenant_id = :tenantId', { tenantId })
      .groupBy('exp.experienceType')
      .getRawMany();

    const byStatus = {} as Record<VerificationStatus, number>;
    statusCounts.forEach((item: any) => {
      byStatus[item.status as VerificationStatus] = parseInt(item.count);
    });

    const byType = {} as Record<ExperienceType, number>;
    typeCounts.forEach((item: any) => {
      byType[item.type as ExperienceType] = parseInt(item.count);
    });

    return { total, byStatus, byType };
  }

  private toORM(entity: SystemExperienceEntity): SystemExperienceORM {
    const orm = new SystemExperienceORM();
    orm.id = entity.id;
    orm.tenantId = this.getTenantId();
    orm.experienceType = entity.experienceType;
    orm.content = entity.content;
    orm.confidence = entity.confidence;
    orm.scenario = entity.scenario;
    orm.relatedCategory = entity.relatedCategory;
    orm.sourceConversationIds = entity.sourceConversationIds;
    orm.verificationStatus = entity.verificationStatus;
    orm.verifiedBy = entity.verifiedBy;
    orm.verifiedAt = entity.verifiedAt;
    orm.usageCount = entity.usageCount;
    orm.positiveCount = entity.positiveCount;
    orm.negativeCount = entity.negativeCount;
    orm.embedding = entity.embedding;
    orm.isActive = entity.isActive;
    orm.createdAt = entity.createdAt;
    orm.updatedAt = entity.updatedAt;
    return orm;
  }

  private toEntity(orm: SystemExperienceORM): SystemExperienceEntity {
    return SystemExperienceEntity.fromPersistence({
      id: orm.id,
      experienceType: orm.experienceType as ExperienceType,
      content: orm.content,
      confidence: orm.confidence,
      scenario: orm.scenario,
      relatedCategory: orm.relatedCategory,
      sourceConversationIds: orm.sourceConversationIds,
      verificationStatus: orm.verificationStatus as VerificationStatus,
      verifiedBy: orm.verifiedBy,
      verifiedAt: orm.verifiedAt,
      usageCount: orm.usageCount,
      positiveCount: orm.positiveCount,
      negativeCount: orm.negativeCount,
      embedding: orm.embedding,
      isActive: orm.isActive,
      createdAt: orm.createdAt,
      updatedAt: orm.updatedAt,
    });
  }

  private toEntityFromRaw(row: any): SystemExperienceEntity {
    return SystemExperienceEntity.fromPersistence({
      id: row.id,
      experienceType: row.experience_type as ExperienceType,
      content: row.content,
      confidence: row.confidence,
      scenario: row.scenario,
      relatedCategory: row.related_category,
      sourceConversationIds: row.source_conversation_ids,
      verificationStatus: row.verification_status as VerificationStatus,
      verifiedBy: row.verified_by,
      verifiedAt: row.verified_at ? new Date(row.verified_at) : undefined,
      usageCount: row.usage_count,
      positiveCount: row.positive_count,
      negativeCount: row.negative_count,
      embedding: row.embedding,
      isActive: row.is_active,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at),
    });
  }
}
