/**
 * AsignacionService - Servicio de asignaciones de vivienda INFONAVIT
 *
 * Gestion de asignaciones de vivienda a derechohabientes.
 *
 * @module Infonavit
 */

import { DataSource, Repository, FindOptionsWhere } from 'typeorm';
import { AsignacionVivienda, AsignacionStatus } from '../entities/asignacion-vivienda.entity';
import { OfertaVivienda } from '../entities/oferta-vivienda.entity';
import { Derechohabiente } from '../entities/derechohabiente.entity';

/**
 * Service context for multi-tenant operations
 */
export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

/**
 * Paginated result
 */
export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateAsignacionDto {
  ofertaId: string;
  derechohabienteId: string;
  assignmentDate: Date;
  creditAmount: number;
  subsidyAmount?: number;
  downPayment?: number;
  notes?: string;
}

export interface FormalizeDto {
  formalizationDate: Date;
  notaryName: string;
  notaryNumber: string;
  deedNumber: string;
  deedDate: Date;
}

export interface AsignacionFilters {
  registroId?: string;
  ofertaId?: string;
  derechohabienteId?: string;
  status?: AsignacionStatus;
  dateFrom?: Date;
  dateTo?: Date;
}

export class AsignacionService {
  private readonly asignacionRepository: Repository<AsignacionVivienda>;
  private readonly ofertaRepository: Repository<OfertaVivienda>;
  private readonly derechohabienteRepository: Repository<Derechohabiente>;

  constructor(dataSource: DataSource) {
    this.asignacionRepository = dataSource.getRepository(AsignacionVivienda);
    this.ofertaRepository = dataSource.getRepository(OfertaVivienda);
    this.derechohabienteRepository = dataSource.getRepository(Derechohabiente);
  }

  private generateAssignmentNumber(): string {
    const now = new Date();
    const year = now.getFullYear().toString().slice(-2);
    const month = (now.getMonth() + 1).toString().padStart(2, '0');
    const random = Math.random().toString(36).substring(2, 8).toUpperCase();
    return `ASG-${year}${month}-${random}`;
  }

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

    const queryBuilder = this.asignacionRepository
      .createQueryBuilder('asig')
      .leftJoinAndSelect('asig.oferta', 'oferta')
      .leftJoinAndSelect('asig.derechohabiente', 'derechohabiente')
      .leftJoinAndSelect('asig.createdBy', 'createdBy')
      .where('asig.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.ofertaId) {
      queryBuilder.andWhere('asig.oferta_id = :ofertaId', { ofertaId: filters.ofertaId });
    }

    if (filters.derechohabienteId) {
      queryBuilder.andWhere('asig.derechohabiente_id = :derechohabienteId', {
        derechohabienteId: filters.derechohabienteId,
      });
    }

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

    if (filters.dateFrom) {
      queryBuilder.andWhere('asig.assignment_date >= :dateFrom', { dateFrom: filters.dateFrom });
    }

    if (filters.dateTo) {
      queryBuilder.andWhere('asig.assignment_date <= :dateTo', { dateTo: filters.dateTo });
    }

    queryBuilder
      .orderBy('asig.assignment_date', 'DESC')
      .skip(skip)
      .take(limit);

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

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

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

  async findWithDetails(ctx: ServiceContext, id: string): Promise<AsignacionVivienda | null> {
    return this.asignacionRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      } as unknown as FindOptionsWhere<AsignacionVivienda>,
      relations: ['oferta', 'oferta.registro', 'derechohabiente', 'createdBy'],
    });
  }

  async create(ctx: ServiceContext, dto: CreateAsignacionDto): Promise<AsignacionVivienda> {
    // Validate oferta exists and is available
    const oferta = await this.ofertaRepository.findOne({
      where: {
        id: dto.ofertaId,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<OfertaVivienda>,
    });

    if (!oferta) {
      throw new Error('Housing offer not found');
    }

    if (oferta.status !== 'available' && oferta.status !== 'reserved') {
      throw new Error('Housing offer is not available for assignment');
    }

    // Validate derechohabiente exists and is qualified
    const derechohabiente = await this.derechohabienteRepository.findOne({
      where: {
        id: dto.derechohabienteId,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<Derechohabiente>,
    });

    if (!derechohabiente) {
      throw new Error('Worker not found');
    }

    if (derechohabiente.status !== 'qualified' && derechohabiente.status !== 'pre_qualified') {
      throw new Error('Worker must be pre-qualified or qualified for assignment');
    }

    // Create assignment
    const asignacion = this.asignacionRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      ofertaId: dto.ofertaId,
      derechohabienteId: dto.derechohabienteId,
      assignmentNumber: this.generateAssignmentNumber(),
      assignmentDate: dto.assignmentDate,
      creditAmount: dto.creditAmount,
      subsidyAmount: dto.subsidyAmount || 0,
      downPayment: dto.downPayment || 0,
      notes: dto.notes,
      status: 'pending',
    });

    const saved = await this.asignacionRepository.save(asignacion);

    // Update oferta status
    oferta.status = 'reserved';
    await this.ofertaRepository.save(oferta);

    return saved;
  }

  async approve(ctx: ServiceContext, id: string): Promise<AsignacionVivienda | null> {
    const asignacion = await this.findWithDetails(ctx, id);
    if (!asignacion) {
      return null;
    }

    if (asignacion.status !== 'pending') {
      throw new Error('Can only approve pending assignments');
    }

    asignacion.status = 'approved';
    asignacion.approvalDate = new Date();
    asignacion.updatedById = ctx.userId || '';

    // Update oferta status
    asignacion.oferta.status = 'assigned';
    await this.ofertaRepository.save(asignacion.oferta);

    // Update derechohabiente status
    await this.derechohabienteRepository.update(
      { id: asignacion.derechohabienteId } as FindOptionsWhere<Derechohabiente>,
      { status: 'assigned', updatedById: ctx.userId || '' }
    );

    return this.asignacionRepository.save(asignacion);
  }

  async formalize(ctx: ServiceContext, id: string, dto: FormalizeDto): Promise<AsignacionVivienda | null> {
    const asignacion = await this.findWithDetails(ctx, id);
    if (!asignacion) {
      return null;
    }

    if (asignacion.status !== 'approved') {
      throw new Error('Can only formalize approved assignments');
    }

    asignacion.status = 'formalized';
    asignacion.formalizationDate = dto.formalizationDate;
    asignacion.notaryName = dto.notaryName;
    asignacion.notaryNumber = dto.notaryNumber;
    asignacion.deedNumber = dto.deedNumber;
    asignacion.deedDate = dto.deedDate;
    asignacion.updatedById = ctx.userId || '';

    return this.asignacionRepository.save(asignacion);
  }

  async deliver(ctx: ServiceContext, id: string, deliveryDate: Date): Promise<AsignacionVivienda | null> {
    const asignacion = await this.findWithDetails(ctx, id);
    if (!asignacion) {
      return null;
    }

    if (asignacion.status !== 'formalized') {
      throw new Error('Can only deliver formalized assignments');
    }

    asignacion.status = 'delivered';
    asignacion.deliveryDate = deliveryDate;
    asignacion.updatedById = ctx.userId || '';

    // Update oferta status
    asignacion.oferta.status = 'delivered';
    await this.ofertaRepository.save(asignacion.oferta);

    return this.asignacionRepository.save(asignacion);
  }

  async cancel(ctx: ServiceContext, id: string, reason: string): Promise<AsignacionVivienda | null> {
    const asignacion = await this.findWithDetails(ctx, id);
    if (!asignacion) {
      return null;
    }

    if (asignacion.status === 'delivered') {
      throw new Error('Cannot cancel delivered assignments');
    }

    const previousStatus = asignacion.status;
    asignacion.status = 'cancelled';
    asignacion.notes = `${asignacion.notes || ''}\n[CANCELLED] ${reason}`;
    asignacion.updatedById = ctx.userId || '';

    // Revert oferta status if it was assigned/reserved
    if (previousStatus === 'approved' || previousStatus === 'formalized') {
      asignacion.oferta.status = 'available';
      await this.ofertaRepository.save(asignacion.oferta);

      // Revert derechohabiente status
      await this.derechohabienteRepository.update(
        { id: asignacion.derechohabienteId } as FindOptionsWhere<Derechohabiente>,
        { status: 'qualified', updatedById: ctx.userId || '' }
      );
    } else if (previousStatus === 'pending') {
      asignacion.oferta.status = 'available';
      await this.ofertaRepository.save(asignacion.oferta);
    }

    return this.asignacionRepository.save(asignacion);
  }
}
