import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { DataSource, FindManyOptions, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListWeightLimitRulesQueryDto } from '../dto/list-rule-engine-query.dto';
import { WeightLimitRule } from '../entities/weight-limit-rule.entity';
import { IWeightLimitRulesRepository } from '../interfaces/weight-limit-rules.repository.interface';

@Injectable()
export class WeightLimitRulesRepository implements IWeightLimitRulesRepository {
  private readonly repo: Repository<WeightLimitRule>;

  constructor(private readonly dataSource: DataSource) {
    this.repo = this.dataSource.getRepository(WeightLimitRule);
  }

  findById(id: string): Promise<WeightLimitRule | null> {
    return this.repo.findOne({
      where: { id },
      relations: { containerType: true },
    });
  }

  findActiveByContainerTypeId(
    containerTypeId: string,
    tradeDirection: string,
  ): Promise<WeightLimitRule[]> {
    return this.repo
      .createQueryBuilder('rule')
      .innerJoinAndSelect('rule.containerType', 'ct')
      .where('rule.container_type_id = :containerTypeId', { containerTypeId })
      .andWhere('(rule.trade_direction = :dir OR rule.trade_direction = :both)', {
        dir: tradeDirection,
        both: 'BOTH',
      })
      .getMany();
  }

  /**
   * Find a rule matching the (containerType, tradeDirection) identity — the
   * tuple enforced by `UQ_weight_limit_rules_pattern`. Used to reject duplicates
   * before insert. Optionally excludes a row by id so updates don't self-collide.
   */
  findByPattern(
    containerTypeId: string,
    tradeDirection: string,
    excludeId?: string,
  ): Promise<WeightLimitRule | null> {
    const qb = this.repo
      .createQueryBuilder('rule')
      .where('rule.container_type_id = :containerTypeId', { containerTypeId })
      .andWhere('rule.trade_direction = :tradeDirection', { tradeDirection });
    if (excludeId) qb.andWhere('rule.id <> :excludeId', { excludeId });
    return qb.getOne();
  }

  findAll(options?: FindManyOptions<WeightLimitRule>): Promise<WeightLimitRule[]> {
    return this.repo.find(options);
  }

  findAndCount(options?: FindManyOptions<WeightLimitRule>): Promise<[WeightLimitRule[], number]> {
    return this.repo.findAndCount(options);
  }

  /**
   * Paged list with the container relation loaded, newest first. `search`
   * matches the trade direction and the joined container type's label/code.
   */
  findPaged(query: ListWeightLimitRulesQueryDto): Promise<PaginatedResponse<WeightLimitRule>> {
    const qb = this.repo
      .createQueryBuilder('rule')
      .leftJoinAndSelect('rule.containerType', 'containerType')
      .orderBy('rule.createdAt', query.sortOrder ?? 'DESC');

    if (query.containerTypeId) {
      qb.andWhere('rule.containerTypeId = :containerTypeId', {
        containerTypeId: query.containerTypeId,
      });
    }
    if (query.tradeDirection) {
      qb.andWhere('rule.tradeDirection = :tradeDirection', { tradeDirection: query.tradeDirection });
    }
    if (query.search) {
      qb.andWhere(
        '(rule.tradeDirection ILIKE :search OR containerType.label ILIKE :search OR containerType.code ILIKE :search)',
        { search: `%${query.search}%` },
      );
    }

    return paginateQuery(qb, query);
  }

  async create(data: Partial<WeightLimitRule>): Promise<WeightLimitRule> {
    const entity = this.repo.create(data);
    return this.repo.save(entity);
  }

  async update(id: string, data: Partial<WeightLimitRule>): Promise<WeightLimitRule | null> {
    await this.repo.update(id, data as never);
    return this.findById(id);
  }

  async softDelete(id: string): Promise<void> {
    await this.repo.softDelete(id);
  }
}
