/**
 * BitacoraObraService - Bitacora de Obra
 *
 * Gestiona el registro diario de bitacora de obra.
 * Genera automaticamente el numero de entrada secuencial.
 *
 * @module Progress
 */

import { DataSource, Repository, IsNull } from 'typeorm';
import { BitacoraObra } from '../entities/bitacora-obra.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 CreateBitacoraDto {
  fraccionamientoId: string;
  entryDate: Date;
  weather?: string;
  temperatureMax?: number;
  temperatureMin?: number;
  workersCount?: number;
  description: string;
  observations?: string;
  incidents?: string;
}

export interface UpdateBitacoraDto {
  weather?: string;
  temperatureMax?: number;
  temperatureMin?: number;
  workersCount?: number;
  description?: string;
  observations?: string;
  incidents?: string;
}

export interface BitacoraFilters {
  dateFrom?: Date;
  dateTo?: Date;
  hasIncidents?: boolean;
}

export class BitacoraObraService {
  private readonly repository: Repository<BitacoraObra>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(BitacoraObra);
  }

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

  /**
   * Create entity
   */
  async create(ctx: ServiceContext, data: Partial<BitacoraObra>): Promise<BitacoraObra> {
    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<BitacoraObra>): Promise<BitacoraObra | 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;
  }

  /**
   * Count entities
   */
  async count(ctx: ServiceContext, where: Partial<BitacoraObra>): Promise<number> {
    return this.repository.count({
      where: {
        ...where,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      } as any,
    });
  }

  /**
   * Find entities
   */
  async find(ctx: ServiceContext, options: { where?: any; order?: any; take?: number }): Promise<BitacoraObra[]> {
    return this.repository.find({
      where: {
        ...options.where,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
      order: options.order,
      take: options.take,
    });
  }

  /**
   * Find one entity
   */
  async findOne(ctx: ServiceContext, where: Partial<BitacoraObra>): Promise<BitacoraObra | null> {
    return this.repository.findOne({
      where: {
        ...where,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      } as any,
    });
  }

  /**
   * Crear nueva entrada de bitacora
   */
  async createEntry(
    ctx: ServiceContext,
    data: CreateBitacoraDto
  ): Promise<BitacoraObra> {
    const entryNumber = await this.getNextEntryNumber(ctx, data.fraccionamientoId);

    return this.create(ctx, {
      ...data,
      entryNumber,
      registeredById: ctx.userId,
    });
  }

  /**
   * Obtener siguiente numero de entrada
   */
  private async getNextEntryNumber(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<number> {
    const result = await this.repository
      .createQueryBuilder('b')
      .select('MAX(b.entry_number)', 'maxNumber')
      .where('b.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('b.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId })
      .getRawOne();

    return (result?.maxNumber || 0) + 1;
  }

  /**
   * Obtener bitacora por fraccionamiento
   */
  async findByFraccionamiento(
    ctx: ServiceContext,
    fraccionamientoId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<BitacoraObra>> {
    const skip = (page - 1) * limit;
    const [data, total] = await this.repository.findAndCount({
      where: {
        tenantId: ctx.tenantId,
        fraccionamientoId,
        deletedAt: IsNull(),
      },
      skip,
      take: limit,
      order: { entryNumber: 'DESC' },
    });

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

  /**
   * Obtener bitacora con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    fraccionamientoId: string,
    filters: BitacoraFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<BitacoraObra>> {
    const qb = this.repository
      .createQueryBuilder('b')
      .where('b.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('b.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId })
      .andWhere('b.deleted_at IS NULL');

    if (filters.dateFrom) {
      qb.andWhere('b.entry_date >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('b.entry_date <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.hasIncidents !== undefined) {
      if (filters.hasIncidents) {
        qb.andWhere('b.incidents IS NOT NULL');
      } else {
        qb.andWhere('b.incidents IS NULL');
      }
    }

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

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

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

  /**
   * Obtener entrada por fecha
   */
  async findByDate(
    ctx: ServiceContext,
    fraccionamientoId: string,
    date: Date
  ): Promise<BitacoraObra | null> {
    return this.findOne(ctx, {
      fraccionamientoId,
      entryDate: date,
    } as any);
  }

  /**
   * Obtener ultima entrada
   */
  async findLatest(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<BitacoraObra | null> {
    const entries = await this.find(ctx, {
      where: { fraccionamientoId } as any,
      order: { entryNumber: 'DESC' },
      take: 1,
    });

    return entries[0] || null;
  }

  /**
   * Obtener estadisticas de bitacora
   */
  async getStats(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<BitacoraStats> {
    const totalEntries = await this.count(ctx, { fraccionamientoId } as any);

    const incidentsCount = await this.repository
      .createQueryBuilder('b')
      .where('b.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('b.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId })
      .andWhere('b.deleted_at IS NULL')
      .andWhere('b.incidents IS NOT NULL')
      .getCount();

    const avgWorkers = await this.repository
      .createQueryBuilder('b')
      .select('AVG(b.workers_count)', 'avg')
      .where('b.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('b.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId })
      .andWhere('b.deleted_at IS NULL')
      .getRawOne();

    return {
      totalEntries,
      entriesWithIncidents: incidentsCount,
      avgWorkersCount: parseFloat(avgWorkers?.avg || '0'),
    };
  }
}

interface BitacoraStats {
  totalEntries: number;
  entriesWithIncidents: number;
  avgWorkersCount: number;
}
