/**
 * DerechohabienteService - Servicio de gestión de derechohabientes INFONAVIT
 *
 * Gestión de trabajadores con crédito INFONAVIT.
 *
 * @module Infonavit
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { Derechohabiente, DerechohabienteStatus } from '../entities/derechohabiente.entity';
import { HistoricoPuntos } from '../entities/historico-puntos.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface CreateDerechohabienteDto {
  nss: string;
  curp: string;
  rfc?: string;
  firstName: string;
  lastName: string;
  secondLastName?: string;
  phone?: string;
  email?: string;
  address?: string;
  creditPoints?: number;
  notes?: string;
}

export interface UpdateDerechohabienteDto {
  phone?: string;
  email?: string;
  address?: string;
  notes?: string;
}

export interface PrecalificationDto {
  precalificationDate: Date;
  precalificationAmount: number;
  creditType: string;
  creditPoints: number;
}

export interface DerechohabienteFilters {
  nss?: string;
  curp?: string;
  status?: DerechohabienteStatus;
  search?: string;
  minPoints?: number;
}

export class DerechohabienteService {
  constructor(
    private readonly derechohabienteRepository: Repository<Derechohabiente>,
    private readonly historicoPuntosRepository: Repository<HistoricoPuntos>
  ) {}

  async findWithFilters(
    ctx: ServiceContext,
    filters: DerechohabienteFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Derechohabiente>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.derechohabienteRepository
      .createQueryBuilder('dh')
      .leftJoinAndSelect('dh.createdBy', 'createdBy')
      .where('dh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('dh.deleted_at IS NULL');

    if (filters.nss) {
      queryBuilder.andWhere('dh.nss = :nss', { nss: filters.nss });
    }

    if (filters.curp) {
      queryBuilder.andWhere('dh.curp = :curp', { curp: filters.curp });
    }

    if (filters.status) {
      queryBuilder.andWhere('dh.status = :status', { status: filters.status });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(dh.first_name ILIKE :search OR dh.last_name ILIKE :search OR dh.nss ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    if (filters.minPoints !== undefined) {
      queryBuilder.andWhere('dh.credit_points >= :minPoints', { minPoints: filters.minPoints });
    }

    queryBuilder
      .orderBy('dh.last_name', 'ASC')
      .addOrderBy('dh.first_name', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  async findById(ctx: ServiceContext, id: string): Promise<Derechohabiente | null> {
    return this.derechohabienteRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Derechohabiente>,
    });
  }

  async findByNss(ctx: ServiceContext, nss: string): Promise<Derechohabiente | null> {
    return this.derechohabienteRepository.findOne({
      where: {
        nss,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Derechohabiente>,
    });
  }

  async findWithDetails(ctx: ServiceContext, id: string): Promise<Derechohabiente | null> {
    return this.derechohabienteRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Derechohabiente>,
      relations: ['createdBy', 'asignaciones', 'asignaciones.oferta', 'historicoPuntos'],
    });
  }

  async create(ctx: ServiceContext, dto: CreateDerechohabienteDto): Promise<Derechohabiente> {
    // Check for existing NSS or CURP
    const existingNss = await this.derechohabienteRepository.findOne({
      where: { nss: dto.nss, tenantId: ctx.tenantId } as FindOptionsWhere<Derechohabiente>,
    });
    if (existingNss) {
      throw new Error('A worker with this NSS already exists');
    }

    const existingCurp = await this.derechohabienteRepository.findOne({
      where: { curp: dto.curp, tenantId: ctx.tenantId } as FindOptionsWhere<Derechohabiente>,
    });
    if (existingCurp) {
      throw new Error('A worker with this CURP already exists');
    }

    const derechohabiente = this.derechohabienteRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      nss: dto.nss,
      curp: dto.curp.toUpperCase(),
      rfc: dto.rfc?.toUpperCase(),
      firstName: dto.firstName,
      lastName: dto.lastName,
      secondLastName: dto.secondLastName,
      phone: dto.phone,
      email: dto.email,
      address: dto.address,
      creditPoints: dto.creditPoints || 0,
      notes: dto.notes,
      status: 'active',
    });

    const saved = await this.derechohabienteRepository.save(derechohabiente);

    // Record initial points if provided
    if (dto.creditPoints && dto.creditPoints > 0) {
      const historico = this.historicoPuntosRepository.create({
        tenantId: ctx.tenantId,
        createdById: ctx.userId,
        derechohabienteId: saved.id,
        recordDate: new Date(),
        previousPoints: 0,
        newPoints: dto.creditPoints,
        pointsChange: dto.creditPoints,
        movementType: 'initial',
        source: 'Registration',
      });
      await this.historicoPuntosRepository.save(historico);
    }

    return saved;
  }

  async update(ctx: ServiceContext, id: string, dto: UpdateDerechohabienteDto): Promise<Derechohabiente | null> {
    const derechohabiente = await this.findById(ctx, id);
    if (!derechohabiente) {
      return null;
    }

    Object.assign(derechohabiente, {
      ...dto,
      updatedById: ctx.userId || '',
    });

    return this.derechohabienteRepository.save(derechohabiente);
  }

  async updatePoints(
    ctx: ServiceContext,
    id: string,
    newPoints: number,
    source: string,
    notes?: string
  ): Promise<Derechohabiente | null> {
    const derechohabiente = await this.findById(ctx, id);
    if (!derechohabiente) {
      return null;
    }

    const previousPoints = derechohabiente.creditPoints;

    // Record points change
    const historico = this.historicoPuntosRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      derechohabienteId: id,
      recordDate: new Date(),
      previousPoints,
      newPoints,
      pointsChange: newPoints - previousPoints,
      movementType: 'update',
      source,
      notes,
    });
    await this.historicoPuntosRepository.save(historico);

    // Update derechohabiente
    derechohabiente.creditPoints = newPoints;
    derechohabiente.updatedById = ctx.userId || '';

    return this.derechohabienteRepository.save(derechohabiente);
  }

  async precalify(ctx: ServiceContext, id: string, dto: PrecalificationDto): Promise<Derechohabiente | null> {
    const derechohabiente = await this.findById(ctx, id);
    if (!derechohabiente) {
      return null;
    }

    if (derechohabiente.status !== 'active') {
      throw new Error('Only active workers can be precalified');
    }

    const previousPoints = derechohabiente.creditPoints;

    // Record points change from precalification
    const historico = this.historicoPuntosRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      derechohabienteId: id,
      recordDate: dto.precalificationDate,
      previousPoints,
      newPoints: dto.creditPoints,
      pointsChange: dto.creditPoints - previousPoints,
      movementType: 'precalification',
      source: 'INFONAVIT Precalification',
    });
    await this.historicoPuntosRepository.save(historico);

    // Update derechohabiente
    derechohabiente.creditPoints = dto.creditPoints;
    derechohabiente.precalificationDate = dto.precalificationDate;
    derechohabiente.precalificationAmount = dto.precalificationAmount;
    derechohabiente.creditType = dto.creditType;
    derechohabiente.status = 'pre_qualified';
    derechohabiente.updatedById = ctx.userId || '';

    return this.derechohabienteRepository.save(derechohabiente);
  }

  async qualify(ctx: ServiceContext, id: string): Promise<Derechohabiente | null> {
    const derechohabiente = await this.findById(ctx, id);
    if (!derechohabiente) {
      return null;
    }

    if (derechohabiente.status !== 'pre_qualified') {
      throw new Error('Only pre-qualified workers can be qualified');
    }

    derechohabiente.status = 'qualified';
    derechohabiente.updatedById = ctx.userId || '';

    return this.derechohabienteRepository.save(derechohabiente);
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const derechohabiente = await this.findById(ctx, id);
    if (!derechohabiente) {
      return false;
    }

    if (derechohabiente.status === 'assigned') {
      throw new Error('Cannot delete workers with assigned housing');
    }

    await this.derechohabienteRepository.update(
      { id, tenantId: ctx.tenantId } as FindOptionsWhere<Derechohabiente>,
      { deletedAt: new Date(), deletedById: ctx.userId || '' }
    );

    return true;
  }

  async getPointsHistory(ctx: ServiceContext, derechohabienteId: string): Promise<HistoricoPuntos[]> {
    return this.historicoPuntosRepository.find({
      where: {
        derechohabienteId,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<HistoricoPuntos>,
      order: { recordDate: 'DESC' },
      relations: ['createdBy'],
    });
  }
}
