import { Body, Controller, Get, Logger, Param, Patch, Post, Put, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GetUser } from 'src/auth/get-user.decorator';
import type { LoginUserInfo } from 'src/auth/dto/login.dto';
import { RequireModule } from 'src/auth/decorators/require-module.decorator';
import { RequirePermission } from 'src/auth/decorators/require-permission.decorator';
import { ModuleGuard } from 'src/auth/guards/module.guard';
import { PermissionGuard } from 'src/auth/guards/permission.guard';
import { MarangatuConfigService } from './marangatu-config.service';
import { MarangatuSyncService, MicroservicioError } from './marangatu-sync.service';
import { UpsertMarangatuConfigDto } from './dto/upsert-marangatu-config.dto';
import { IsArray, IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator';

class SyncDto {
  @IsNumber()
  anho: number;

  @IsNumber()
  mes: number;

  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  cdcs?: string[];
}

class RecuperarCdcDto {
  @IsArray()
  @IsString({ each: true })
  cdcs: string[];

  /** Documentos por llamada a SIFEN. Default 20. */
  @IsOptional()
  @IsNumber()
  lote_size?: number;

  /** Solo consulta y reporta qué se recuperaría, sin escribir nada. */
  @IsOptional()
  @IsBoolean()
  dry_run?: boolean;

  /**
   * Deja las facturas recuperadas con saldo cero. Usar cuando el histórico se carga
   * solo a efectos fiscales y las cobranzas ya se gestionaron fuera del sistema: sin
   * esto, las facturas a crédito reviven como deuda que el cliente ya pagó.
   */
  @IsOptional()
  @IsBoolean()
  sin_cartera?: boolean;
}

class ConsultaDto extends SyncDto {
  @IsString()
  tipoRegistro: 'COMPRAS' | 'VENTAS';
}

@UseGuards(AuthGuard('jwt'), ModuleGuard, PermissionGuard)
@RequireModule('FISCAL')
@Controller('marangatu')
export class MarangatuController {
  private readonly logger = new Logger(MarangatuController.name);

  constructor(
    private readonly configService: MarangatuConfigService,
    private readonly syncService: MarangatuSyncService,
  ) {}

  @Get('config')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_VER')
  getConfig(@GetUser() user: LoginUserInfo) {
    return this.configService.getConfig(user.empresa_id);
  }

  @Put('config')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  async upsertConfig(@GetUser() user: LoginUserInfo, @Body() dto: UpsertMarangatuConfigDto) {
    const result = await this.configService.upsertConfig(user.empresa_id, dto);
    // El alta en el bot no puede bloquear el guardado de la config, pero un
    // fallo tiene que quedar en el log: antes se tragaba y la captura manual
    // terminaba en un 500 sin pista. El 409 es el caso normal (ya estaba dada
    // de alta); ojo que el bot no actualiza la contraseña en ese caso.
    await this.syncService.registrarEmpresa(user.empresa_id).catch((err: unknown) => {
      if (err instanceof MicroservicioError && err.status === 409) return;
      this.logger.warn(`Alta en bot Marangatu falló para empresa ${user.empresa_id}: ${err instanceof Error ? err.message : err}`);
    });
    return result;
  }

  @Post('sync/compras')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  syncCompras(@GetUser() user: LoginUserInfo, @Body() dto: SyncDto) {
    return this.syncService.triggerSync(user.empresa_id, 'COMPRAS', dto);
  }

  @Post('sync/ventas')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  syncVentas(@GetUser() user: LoginUserInfo, @Body() dto: SyncDto) {
    return this.syncService.triggerSync(user.empresa_id, 'VENTAS', dto);
  }

  @Post('importar/compras')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  importarCompras(@GetUser() user: LoginUserInfo, @Body() dto: SyncDto) {
    return this.syncService.importarCompras(user.empresa_id, dto);
  }

  /**
   * Marca como electrónicos los comprobantes del período que la DNIT ya tiene, para
   * que no se vuelvan a declarar en el Libro IVA Compras. No importa comprobantes ni
   * modifica montos, y es idempotente.
   */
  @Post('marcar-electronicos')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  marcarElectronicos(@GetUser() user: LoginUserInfo, @Body() dto: SyncDto) {
    return this.syncService.sincronizarMarcasElectronicos(user.empresa_id, dto);
  }

  /**
   * Recupera comprobantes históricos consultando cada CDC directamente a SIFEN.
   *
   * El import normal de Marangatu solo llega hasta donde la DNIT publica el período en
   * el portal; para comprobantes viejos no hay de dónde traerlos. SIFEN sí responde la
   * consulta por CDC sin límite de antigüedad, así que con la lista de CDC se
   * reconstruye el histórico. Importa facturas y notas de crédito, y es idempotente:
   * se puede reintentar el mismo lote sin duplicar.
   */
  @Post('recuperar/por-cdc')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  recuperarPorCdc(@GetUser() user: LoginUserInfo, @Body() dto: RecuperarCdcDto) {
    return this.syncService.recuperarPorCdc(user.empresa_id, dto.cdcs, {
      loteSize: dto.lote_size,
      dryRun: dto.dry_run,
      sinCartera: dto.sin_cartera,
    });
  }

  @Post('importar/ventas')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  importarVentas(@GetUser() user: LoginUserInfo, @Body() dto: SyncDto) {
    return this.syncService.importarVentas(user.empresa_id, dto);
  }

  @Post('importar/notas-credito')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  importarNotasCredito(@GetUser() user: LoginUserInfo, @Body() dto: SyncDto) {
    return this.syncService.importarNotasCredito(user.empresa_id, dto);
  }

  @Post('consultar')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_VER')
  consultarDocumentos(@GetUser() user: LoginUserInfo, @Body() dto: ConsultaDto) {
    return this.syncService.consultarDocumentos(user.empresa_id, dto.tipoRegistro, dto);
  }

  @Get('pendientes')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_VER')
  getPendientes(@GetUser() user: LoginUserInfo) {
    return this.syncService.getPendientes(user.empresa_id);
  }

  @Patch('det/:detId/vincular')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  vincularDetalle(
    @GetUser() user: LoginUserInfo,
    @Param('detId') detId: string,
    @Body() body: { producto_id: string | null },
  ) {
    return this.syncService.vincularDetalle(detId, user.empresa_id, body.producto_id ?? null, user.id);
  }

  @Patch(':id/condicion')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  cambiarCondicionCompra(
    @GetUser() user: LoginUserInfo,
    @Param('id') compraId: string,
    @Body() body: {
      condicion_pago_id?: string | null;
      es_credito?: boolean;
      plazo_dias?: number;
      cuotas?: number;
    },
  ) {
    return this.syncService.cambiarCondicionCompra(compraId, user.empresa_id, body, user.id);
  }

  @Post('rechazar/:id')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  rechazarCompra(
    @GetUser() user: LoginUserInfo,
    @Param('id') compraId: string,
    @Body() body: { motivo?: string },
  ) {
    return this.syncService.rechazarCompra(compraId, user.empresa_id, body?.motivo, user.id);
  }

  @Post('confirmar/:id')
  @RequirePermission('FISCAL', 'FIS_MAR_MARANGATU_GENERAR')
  confirmarCompra(
    @GetUser() user: LoginUserInfo,
    @Param('id') compraId: string,
    @Body() body: { depositoId?: string },
  ) {
    return this.syncService.confirmarCompra(compraId, user.empresa_id, body?.depositoId, user.id);
  }
}
