import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { Brackets, DataSource, In, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListYardDistancesQueryDto } from '../dto/list-rule-engine-query.dto';
import { YardDistance } from '../entities/yard-distance.entity';
import { IYardDistancesRepository } from '../interfaces/yard-distances.repository.interface';

@Injectable()
export class YardDistancesRepository implements IYardDistancesRepository {
  private readonly repo: Repository<YardDistance>;

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

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

  findBetween(fromYardId: string, toYardId: string): Promise<YardDistance | null> {
    return this.repo.findOne({
      where: [
        { fromYardId, toYardId },
        { fromYardId: toYardId, toYardId: fromYardId },
      ],
    });
  }

  findTouchingYards(yardIds: string[]): Promise<YardDistance[]> {
    if (!yardIds.length) return Promise.resolve([]);
    return this.repo.find({
      where: [{ fromYardId: In(yardIds) }, { toYardId: In(yardIds) }],
    });
  }

  /** Paged list with server-side search on either yard's label/code. */
  findPaged(query: ListYardDistancesQueryDto): Promise<PaginatedResponse<YardDistance>> {
    const qb = this.repo
      .createQueryBuilder('yardDistance')
      .leftJoinAndSelect('yardDistance.fromYard', 'fromYard')
      .leftJoinAndSelect('yardDistance.toYard', 'toYard')
      .orderBy(`yardDistance.${query.sortBy ?? 'createdAt'}`, query.sortOrder ?? 'ASC')
      .addOrderBy('fromYard.label', 'ASC');

    if (query.yardId) {
      qb.andWhere(
        new Brackets((w) =>
          w
            .where('yardDistance.fromYardId = :yardId', { yardId: query.yardId })
            .orWhere('yardDistance.toYardId = :yardId', { yardId: query.yardId }),
        ),
      );
    }
    if (query.search) {
      qb.andWhere(
        new Brackets((w) =>
          w
            .where('fromYard.label ILIKE :search', { search: `%${query.search}%` })
            .orWhere('fromYard.code ILIKE :search', { search: `%${query.search}%` })
            .orWhere('toYard.label ILIKE :search', { search: `%${query.search}%` })
            .orWhere('toYard.code ILIKE :search', { search: `%${query.search}%` }),
        ),
      );
    }

    return paginateQuery(qb, query);
  }

  async create(data: Partial<YardDistance>): Promise<YardDistance> {
    const entity = this.repo.create(data);
    const saved = await this.repo.save(entity);
    return (await this.findById(saved.id)) ?? saved;
  }

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

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