import { BadRequestException, Body, Controller, ForbiddenException, Get, Param, ParseIntPipe, Post, Put, Request, UseGuards, UseInterceptors } from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import { nivelConfiguracion, tieneAlgunAccesoConfiguracion } from "../auth/default-permissions";
import { JwtAuthGuard } from "../auth/guards/jwt-auth.guard";
import { PermissionGuard } from "../auth/guards/permission.guard";
import { PostouchClientService } from "../facturacion-pos/postouch-client.service";
import { TenantGuard } from "../tenancy/tenant.guard";
import { TenantReleaseInterceptor } from "../tenancy/tenant-release.interceptor";
import { ActualizarParametrosTenantDto } from "./dto/actualizar-parametros-tenant.dto";
import { ProbarConexionPosDto } from "./dto/probar-conexion-pos.dto";
import { entradaDe, TALLER_PARAMETROS_CATALOGO } from "./taller-parametros.catalogo";
import { dtoAValoresTenant } from "./taller-parametros.mapeo";
import { TallerParametrosService } from "./taller-parametros.service";

// tiendaId=null en facturacionPosTiendaIds (o el campo ausente) = sin
// restriccion -- ve/edita todas las que su nivel de "configuracion.facturacion"
// ya le permita. Ver comentario de la columna en usuario.entity.ts.
function tiendaFacturacionPermitida(user: any, tiendaId: number): boolean {
  const restriccion = user?.facturacionPosTiendaIds;
  if (!Array.isArray(restriccion)) return true;
  return restriccion.includes(tiendaId);
}

@ApiTags("taller-parametros")
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, TenantGuard, PermissionGuard)
@UseInterceptors(TenantReleaseInterceptor)
@Controller("taller-parametros")
export class TallerParametrosController {
  constructor(
    private readonly tallerParametrosService: TallerParametrosService,
    private readonly postouchClientService: PostouchClientService,
  ) {}

  // Sin @RequirePermission a proposito: es solo metadata (etiquetas,
  // grupos, tipos -- nunca valores) para que el frontend sepa que renderizar
  // por grupo antes de saber que grupos puede LEER este usuario en
  // particular -- ese filtro pasa en getResumen()/el propio frontend, no aca.
  @Get("catalogo")
  @ApiOperation({ summary: "Metadata del catálogo de parámetros (sin valores), para renderizar el formulario" })
  getCatalogo() {
    return TALLER_PARAMETROS_CATALOGO;
  }

  @Get()
  @ApiOperation({ summary: "Resumen de configuración del tenant (valores actuales, sensibles enmascarados)" })
  async getResumen(@Request() req: any) {
    if (!tieneAlgunAccesoConfiguracion(req.user.permissions)) {
      throw new ForbiddenException("Sin acceso a ningún grupo de configuración.");
    }
    return this.tallerParametrosService.getResumenTenant({
      puedeLeerGrupo: (grupo) => nivelConfiguracion(req.user.permissions, grupo) !== "none",
      tiendaPermitida: (tiendaId) => tiendaFacturacionPermitida(req.user, tiendaId),
    });
  }

  // Sin @RequirePermission a propósito: cualquier usuario autenticado del
  // tenant necesita saber si debe ocultar "Facturar"/"Nueva tienda"/"Nuevo
  // producto" -- no solo quien administra la configuración.
  @Get("flags")
  @ApiOperation({ summary: "Flags booleanos no sensibles para que el frontend oculte acciones" })
  async getFlags() {
    return this.tallerParametrosService.getFlags();
  }

  @Put("tenant")
  @ApiOperation({ summary: "Actualizar parámetros de ámbito tenant (facturación, inventario, productos, tiendas, WhatsApp)" })
  async actualizarTenant(@Body() dto: ActualizarParametrosTenantDto, @Request() req: any) {
    const valores = dtoAValoresTenant(dto);

    // Chequeo por clave, no por endpoint entero: un PUT de este form puede
    // mezclar claves de varios grupos en un solo batch (ej. WhatsApp +
    // facturación). Defensa en profundidad -- el frontend ya oculta los
    // campos que el usuario no puede editar, esto es lo que pasa si ese
    // filtro del lado cliente se rompe o alguien pega el body a mano.
    for (const clave of Object.keys(valores) as Array<keyof typeof valores>) {
      const grupo = entradaDe(clave).grupo;
      if (nivelConfiguracion(req.user.permissions, grupo) !== "write") {
        throw new ForbiddenException(
          `Sin permiso de escritura para el grupo "${grupo}" (clave "${String(clave)}").`,
        );
      }
    }

    await this.tallerParametrosService.setGrupo(valores, req.user.usuarioLocalId);
    return { ok: true };
  }

  @Post("tiendas/:id/facturacion-pos/probar")
  @ApiOperation({ summary: "Probar credenciales de facturación POS contra POSTouch, sin crear ninguna orden" })
  async probarConexionPos(@Param("id", ParseIntPipe) id: number, @Body() dto: ProbarConexionPosDto, @Request() req: any) {
    if (
      nivelConfiguracion(req.user.permissions, "facturacion") !== "write" ||
      !tiendaFacturacionPermitida(req.user, id)
    ) {
      throw new ForbiddenException("Sin permiso de facturación POS para esta tienda.");
    }

    let password = dto.password;
    if (!password) {
      const urlGuardada = await this.tallerParametrosService.getParaTienda(id, "facturacion_pos.url");
      if (dto.url !== urlGuardada) {
        throw new BadRequestException(
          "La URL no coincide con la guardada -- ingresá la contraseña para probar contra una URL distinta.",
        );
      }
      password = await this.tallerParametrosService.getParaTienda(id, "facturacion_pos.password");
      if (!password) {
        throw new BadRequestException(
          "No hay contraseña guardada para esta tienda -- ingresá una para poder probar.",
        );
      }
    }

    return this.postouchClientService.probarConexion({
      url: dto.url,
      username: dto.username,
      password,
      computadora: dto.computadora,
    });
  }
}
