/**
 * AvanceObraService - Gestion de Avances de Obra
 *
 * Gestiona el registro y aprobacion de avances fisicos de obra.
 * Incluye workflow de captura -> revision -> aprobacion.
 *
 * @module Progress
 */

import { DataSource, Repository, IsNull } from 'typeorm';
import { AvanceObra, AdvanceStatus } from '../entities/avance-obra.entity';
import { FotoAvance } from '../entities/foto-avance.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 CreateAvanceDto {
  loteId?: string;
  departamentoId?: string;
  conceptoId: string;
  captureDate: Date;
  quantityExecuted: number;
  percentageExecuted?: number;
  notes?: string;
}

export interface AddFotoDto {
  fileUrl: string;
  fileName?: string;
  fileSize?: number;
  mimeType?: string;
  description?: string;
  location?: { lat: number; lng: number };
}

export interface AvanceFilters {
  loteId?: string;
  departamentoId?: string;
  conceptoId?: string;
  status?: AdvanceStatus;
  dateFrom?: Date;
  dateTo?: Date;
}

export class AvanceObraService {
  private readonly repository: Repository<AvanceObra>;
  private readonly fotoRepository: Repository<FotoAvance>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(AvanceObra);
    this.fotoRepository = dataSource.getRepository(FotoAvance);
  }

  /**
   * Find by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<AvanceObra | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Create entity
   */
  async create(ctx: ServiceContext, data: Partial<AvanceObra>): Promise<AvanceObra> {
    const entity = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
    });
    return this.repository.save(entity);
  }

  /**
   * Update entity
   */
  async update(ctx: ServiceContext, id: string, data: Partial<AvanceObra>): Promise<AvanceObra | null> {
    const entity = await this.findById(ctx, id);
    if (!entity) {
      return null;
    }
    Object.assign(entity, data, { updatedById: ctx.userId });
    return this.repository.save(entity);
  }

  /**
   * Soft delete entity
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const entity = await this.findById(ctx, id);
    if (!entity) {
      return false;
    }
    entity.deletedAt = new Date();
    entity.deletedById = ctx.userId ?? null;
    await this.repository.save(entity);
    return true;
  }

  /**
   * Crear nuevo avance (captura)
   */
  async createAvance(
    ctx: ServiceContext,
    data: CreateAvanceDto
  ): Promise<AvanceObra> {
    if (!data.loteId && !data.departamentoId) {
      throw new Error('Either loteId or departamentoId is required');
    }

    if (data.loteId && data.departamentoId) {
      throw new Error('Cannot specify both loteId and departamentoId');
    }

    return this.create(ctx, {
      ...data,
      status: 'captured',
      capturedById: ctx.userId,
    });
  }

  /**
   * Obtener avances por lote
   */
  async findByLote(
    ctx: ServiceContext,
    loteId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<AvanceObra>> {
    const skip = (page - 1) * limit;
    const [data, total] = await this.repository.findAndCount({
      where: {
        tenantId: ctx.tenantId,
        loteId,
        deletedAt: IsNull(),
      },
      skip,
      take: limit,
      order: { createdAt: 'DESC' },
    });

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

  /**
   * Obtener avances por departamento
   */
  async findByDepartamento(
    ctx: ServiceContext,
    departamentoId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<AvanceObra>> {
    const skip = (page - 1) * limit;
    const [data, total] = await this.repository.findAndCount({
      where: {
        tenantId: ctx.tenantId,
        departamentoId,
        deletedAt: IsNull(),
      },
      skip,
      take: limit,
      order: { createdAt: 'DESC' },
    });

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

  /**
   * Obtener avances con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: AvanceFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<AvanceObra>> {
    const qb = this.repository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.deleted_at IS NULL');

    if (filters.loteId) {
      qb.andWhere('a.lote_id = :loteId', { loteId: filters.loteId });
    }
    if (filters.departamentoId) {
      qb.andWhere('a.departamento_id = :departamentoId', { departamentoId: filters.departamentoId });
    }
    if (filters.conceptoId) {
      qb.andWhere('a.concepto_id = :conceptoId', { conceptoId: filters.conceptoId });
    }
    if (filters.status) {
      qb.andWhere('a.status = :status', { status: filters.status });
    }
    if (filters.dateFrom) {
      qb.andWhere('a.capture_date >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('a.capture_date <= :dateTo', { dateTo: filters.dateTo });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('a.capture_date', 'DESC').skip(skip).take(limit);

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

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

  /**
   * Obtener avance con fotos
   */
  async findWithFotos(ctx: ServiceContext, id: string): Promise<AvanceObra | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
      relations: ['fotos', 'concepto', 'capturedBy'],
    });
  }

  /**
   * Agregar foto al avance
   */
  async addFoto(
    ctx: ServiceContext,
    avanceId: string,
    data: AddFotoDto
  ): Promise<FotoAvance> {
    const avance = await this.findById(ctx, avanceId);
    if (!avance) {
      throw new Error('Avance not found');
    }

    const location = data.location
      ? `POINT(${data.location.lng} ${data.location.lat})`
      : null;

    const foto = this.fotoRepository.create({
      tenantId: ctx.tenantId,
      avanceId,
      fileUrl: data.fileUrl,
      fileName: data.fileName,
      fileSize: data.fileSize,
      mimeType: data.mimeType,
      description: data.description,
      location,
      createdById: ctx.userId,
    });

    return this.fotoRepository.save(foto);
  }

  /**
   * Revisar avance
   */
  async review(ctx: ServiceContext, avanceId: string): Promise<AvanceObra | null> {
    const avance = await this.findById(ctx, avanceId);
    if (!avance || avance.status !== 'captured') {
      return null;
    }

    return this.update(ctx, avanceId, {
      status: 'reviewed',
      reviewedById: ctx.userId,
      reviewedAt: new Date(),
    });
  }

  /**
   * Aprobar avance
   */
  async approve(ctx: ServiceContext, avanceId: string): Promise<AvanceObra | null> {
    const avance = await this.findById(ctx, avanceId);
    if (!avance || avance.status !== 'reviewed') {
      return null;
    }

    return this.update(ctx, avanceId, {
      status: 'approved',
      approvedById: ctx.userId,
      approvedAt: new Date(),
    });
  }

  /**
   * Rechazar avance
   */
  async reject(
    ctx: ServiceContext,
    avanceId: string,
    reason: string
  ): Promise<AvanceObra | null> {
    const avance = await this.findById(ctx, avanceId);
    if (!avance || !['captured', 'reviewed'].includes(avance.status)) {
      return null;
    }

    return this.update(ctx, avanceId, {
      status: 'rejected',
      notes: reason,
    });
  }

  /**
   * Calcular avance acumulado por concepto
   */
  async getAccumulatedProgress(
    ctx: ServiceContext,
    loteId?: string,
    departamentoId?: string
  ): Promise<ConceptProgress[]> {
    const qb = this.repository
      .createQueryBuilder('a')
      .select('a.concepto_id', 'conceptoId')
      .addSelect('SUM(a.quantity_executed)', 'totalQuantity')
      .addSelect('AVG(a.percentage_executed)', 'avgPercentage')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.deleted_at IS NULL')
      .andWhere('a.status = :status', { status: 'approved' })
      .groupBy('a.concepto_id');

    if (loteId) {
      qb.andWhere('a.lote_id = :loteId', { loteId });
    }
    if (departamentoId) {
      qb.andWhere('a.departamento_id = :departamentoId', { departamentoId });
    }

    return qb.getRawMany();
  }
}

interface ConceptProgress {
  conceptoId: string;
  totalQuantity: number;
  avgPercentage: number;
}
