import { ConflictException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { AuditService } from 'src/audit/audit.service';
import { QueuesService } from 'src/queues/queues.service';
import { EmpresasService } from '../empresas/empresas.service';
import { PrismaService } from '../prisma/prisma.service';
import { CreateSucursaleDto } from './dto/create-sucursal.dto';
import { UpdateSucursaleDto } from './dto/update-sucursale.dto';
import { tieneAccesoElevado } from 'src/utils/acceso-elevado';
import { alcanceSucursalUsuario } from 'src/utils/alcance-sucursal';
import { rubroDelRequest } from 'src/utils/rubro-context';

@Injectable()
export class SucursalesService {
  private readonly logger = new Logger(SucursalesService.name);
  constructor(
    private readonly empresasService: EmpresasService,
    private readonly prisma: PrismaService,
    private readonly queuesService: QueuesService,
    private readonly auditService: AuditService,
  ) {}
  async create(createSucursaleDto: CreateSucursaleDto, empresa_id: string, user_id?: string) {
    try {
      const empresa = await this.empresasService.findOne(empresa_id);

      if (!empresa) throw new Error('Empresa no encontrada');

      const puntoEstablecimientoExiste = await this.prisma.empresas_sucursales.findFirst({
        where: {
          punto_establecimiento: createSucursaleDto?.punto_establecimiento,
          empresa_id,
        },
      });

      if (puntoEstablecimientoExiste) throw new ConflictException('Punto establecimiento ya existe');

      const sucursal = await this.prisma.empresas_sucursales.create({
        data: createSucursaleDto,
      });

      // Sincronizar con middleware (Modificar empresa porque se agrega sucursal)
      if (sucursal.empresa_id) {
        await this.queuesService.enqueueMiddlewareEmpresa(sucursal.empresa_id, 'M', 'empresas');
      }

      this.logger.log(`Sucursal creada: ${sucursal.id} | Empresa: ${empresa_id}`, 'SucursalesService');
      await this.auditService.log({
        empresa_id,
        user_id,
        action: 'CREATE',
        entity_type: 'sucursal',
        entity_id: sucursal.id,
        descripcion: `Sucursal creada: ${createSucursaleDto.punto_establecimiento ?? sucursal.id} | Empresa: ${empresa_id}`,
        new_value: { punto_establecimiento: createSucursaleDto.punto_establecimiento },
      });
      return { message: 'Sucursal creada exitosamente', data: sucursal };
    } catch (error) {
      if (error instanceof ConflictException) {
        throw error;
      }
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al crear sucursal: ${message}`);
    }
  }

  async findAll(page = 1, limit = 10) {
    const take = Math.max(1, Math.min(100, Number(limit) || 10));
    const currentPage = Math.max(1, Number(page) || 1);
    const skip = (currentPage - 1) * take;

    const [items, total] = await this.prisma.$transaction([
      this.prisma.empresas_sucursales.findMany({
        skip,
        take,
        orderBy: [{ es_default: 'desc' }, { created_at: 'desc' }],
      }),
      this.prisma.empresas_sucursales.count(),
    ]);

    return {
      items,
      total,
      page: currentPage,
      limit: take,
      lastPage: Math.max(1, Math.ceil(total / take) || 1),
    };
  }

  async findAllByEmpresa(empresa_id: string | undefined, page = 1, limit = 10, usuario_id?: string) {
    const take = Math.max(1, Math.min(100, Number(limit) || 10));
    const currentPage = Math.max(1, Number(page) || 1);
    const skip = (currentPage - 1) * take;

    // El usuario solo ve las sucursales de su alcance: las asignadas y, con un
    // rubro elegido en el sidebar, sólo las de ese rubro (también para un
    // superadmin, que sin rubro ve todas). Es el mismo `alcanceSucursalUsuario`
    // que recorta listados, reportes y dashboards, así los selectores de
    // sucursal (Stock, Ajuste, Transferencias) ofrecen lo mismo que se ve.
    // El permiso de la pantalla se sigue evaluando aparte, en el controller.
    const alcance = await alcanceSucursalUsuario(this.prisma, usuario_id, empresa_id);
    const sucursalesAlcance = alcance?.sucursalIds ?? [];

    const whereBase = empresa_id ? { empresa_id } : {};

    // Sin restricción → todas. Con restricción por asignación, bypass mirando el
    // RESULTADO: si no le deja ninguna sucursal de esta empresa (asignaciones a
    // otra empresa, o a sucursales borradas), devolvemos todas en vez de dejarlo
    // con la pantalla vacía. Con rubro elegido no hay bypass: un rubro sin
    // sucursal en esta empresa es una lista vacía, igual que en los listados.
    let where: typeof whereBase & { id?: { in: string[] } } = whereBase;
    if (alcance !== null) {
      const conAlcance = { ...whereBase, id: { in: sucursalesAlcance } };
      const hay = sucursalesAlcance.length ? await this.prisma.empresas_sucursales.count({ where: conAlcance }) : 0;
      if (hay > 0 || rubroDelRequest()) where = conAlcance;
    }

    const [items, total] = await this.prisma.$transaction([
      this.prisma.empresas_sucursales.findMany({
        where,
        skip,
        take,
        orderBy: [{ es_default: 'desc' }, { created_at: 'desc' }],
      }),
      this.prisma.empresas_sucursales.count({ where }),
    ]);

    return {
      items,
      total,
      page: currentPage,
      limit: take,
      lastPage: Math.max(1, Math.ceil(total / take) || 1),
    };
  }

  async findOne(id: string) {
    try {
      const sucursal = await this.prisma.empresas_sucursales.findUnique({
        where: { id },
      });
      if (!sucursal) throw new NotFoundException('Sucursal no encontrada');
      return { message: 'Sucursal encontrada exitosamente', data: sucursal };
    } catch (error) {
      if (error instanceof NotFoundException) throw error;
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al buscar sucursal: ${message}`);
    }
  }

  async findOneWithRelations(id: string) {
    const sucursal = await this.prisma.empresas_sucursales.findUnique({
      where: { id },
      include: {
        cajas: {
          include: {
            puntos_expedicion: {
              include: {
                numeraciones_documento: {
                  include: {
                    tipo_documento: true,
                    timbrado: true,
                  },
                },
              },
            },
          },
        },
        depositos: true,
        asignaciones_usuarios: {
          include: {
            usuario: {
              select: {
                id: true,
                nombres: true,
                email: true,
              },
            },
          },
        },
        paises: true,
        departamentos: true,
        distritos: true,
        ciudades: true,
        barrios: true,
      },
    });

    if (!sucursal) throw new NotFoundException('Sucursal no encontrada');

    return { message: 'Sucursal encontrada exitosamente', data: sucursal };
  }

  async update(id: string, updateSucursaleDto: UpdateSucursaleDto, user_id?: string) {
    try {
      const sucursalFind = await this.prisma.empresas_sucursales.findUnique({
        where: { id },
      });
      if (!sucursalFind) throw new NotFoundException('Sucursal no encontrada');

      const sucursal = await this.prisma.empresas_sucursales.update({
        where: { id },
        data: updateSucursaleDto,
      });

      // Sincronizar con middleware
      if (sucursal.empresa_id) {
        await this.queuesService.enqueueMiddlewareEmpresa(sucursal.empresa_id, 'M', 'empresas');
      }

      this.logger.log(`Sucursal actualizada: ${id}`, 'SucursalesService');
      await this.auditService.log({
        empresa_id: sucursalFind.empresa_id,
        user_id,
        action: 'UPDATE',
        entity_type: 'sucursal',
        entity_id: id,
        descripcion: `Sucursal actualizada: ${sucursalFind.punto_establecimiento ?? id}`,
        old_value: { punto_establecimiento: sucursalFind.punto_establecimiento, active: sucursalFind.active },
        new_value: updateSucursaleDto as Record<string, unknown>,
      });
      return { message: 'Sucursal actualizada exitosamente', data: sucursal };
    } catch (error) {
      if (error instanceof NotFoundException) throw error;
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al actualizar sucursal: ${message}`);
    }
  }

  async activarDesactivar(id: string, user_id?: string) {
    try {
      const sucursalFind = await this.prisma.empresas_sucursales.findUnique({
        where: { id },
      });
      if (!sucursalFind) throw new Error('Sucursal no encontrada');
      const sucursal = await this.prisma.empresas_sucursales.update({
        where: { id },
        data: { active: !sucursalFind.active },
      });

      // Sincronizar con middleware
      if (sucursal.empresa_id) {
        await this.queuesService.enqueueMiddlewareEmpresa(sucursal.empresa_id, 'M', 'sucursales');
      }

      const action = sucursal.active ? 'ACTIVATE' : 'DEACTIVATE';
      this.logger.log(`Sucursal (${id}) ${sucursal.active ? 'activada' : 'desactivada'}`, 'SucursalesService');
      await this.auditService.log({
        empresa_id: sucursalFind.empresa_id,
        user_id,
        action,
        entity_type: 'sucursal',
        entity_id: id,
        descripcion: `Sucursal ${sucursal.active ? 'activada' : 'desactivada'}: ${sucursalFind.punto_establecimiento ?? id}`,
        old_value: { active: sucursalFind.active },
        new_value: { active: sucursal.active },
      });
      return { message: 'Sucursal actualizada exitosamente', data: sucursal };
    } catch (error) {
      console.log('Error al actualizar sucursal:', error);
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al actualizar sucursal: ${message}`);
    }
  }

  async remove(id: string, user_id?: string) {
    try {
      const sucursalFind = await this.prisma.empresas_sucursales.findUnique({ where: { id } });
      const sucursal = await this.prisma.empresas_sucursales.delete({
        where: { id },
      });

      // Sincronizar con middleware
      if (sucursal.empresa_id) {
        await this.queuesService.enqueueMiddlewareEmpresa(sucursal.empresa_id, 'B', 'empresas');
      }

      this.logger.log(`Sucursal eliminada: ${id}`, 'SucursalesService');
      await this.auditService.log({
        empresa_id: sucursal.empresa_id,
        user_id,
        action: 'DELETE',
        entity_type: 'sucursal',
        entity_id: id,
        descripcion: `Sucursal eliminada: ${sucursalFind?.punto_establecimiento ?? id} | Empresa: ${sucursal.empresa_id}`,
        old_value: { punto_establecimiento: sucursalFind?.punto_establecimiento, active: sucursalFind?.active },
      });
      return { message: 'Sucursal eliminada exitosamente', data: sucursal };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al eliminar sucursal: ${message}`);
    }
  }

  async setDefault(id: string, user_id?: string) {
    const sucursal = await this.prisma.empresas_sucursales.findUnique({ where: { id } });
    if (!sucursal) throw new NotFoundException('Sucursal no encontrada');
    if (!sucursal.empresa_id) throw new ConflictException('Sucursal sin empresa_id');
    if (!sucursal.active) throw new ConflictException('No se puede marcar como predeterminada una sucursal inactiva');

    const empresa_id = sucursal.empresa_id;
    await this.prisma.$transaction([
      this.prisma.empresas_sucursales.updateMany({
        where: { empresa_id, es_default: true, NOT: { id } },
        data: { es_default: false },
      }),
      this.prisma.empresas_sucursales.update({
        where: { id },
        data: { es_default: true },
      }),
    ]);

    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'sucursal',
      entity_id: id,
      descripcion: `Sucursal marcada como predeterminada: ${sucursal.punto_establecimiento ?? id}`,
      old_value: { es_default: sucursal.es_default },
      new_value: { es_default: true },
    });

    return { message: 'Sucursal marcada como predeterminada', data: { id, es_default: true } };
  }

  async unsetDefault(id: string, user_id?: string) {
    const sucursal = await this.prisma.empresas_sucursales.findUnique({ where: { id } });
    if (!sucursal) throw new NotFoundException('Sucursal no encontrada');
    if (!sucursal.es_default) {
      return { message: 'La sucursal no estaba marcada como predeterminada', data: { id, es_default: false } };
    }

    await this.prisma.empresas_sucursales.update({
      where: { id },
      data: { es_default: false },
    });

    await this.auditService.log({
      empresa_id: sucursal.empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'sucursal',
      entity_id: id,
      descripcion: `Sucursal desmarcada como predeterminada: ${sucursal.punto_establecimiento ?? id}`,
      old_value: { es_default: true },
      new_value: { es_default: false },
    });

    return { message: 'Sucursal desmarcada como predeterminada', data: { id, es_default: false } };
  }

  /**
   * Guarda la URL del logo propio de la sucursal.
   *
   * `null` la borra y vuelve a caer en el logo de la empresa, que es el
   * comportamiento por defecto de toda sucursal que nunca subió uno.
   *
   * Encola el sync con el middleware igual que el resto de los cambios de
   * sucursal: el KUDE lo arma el middleware con los datos que tiene guardados,
   * así que un logo que no viaje allá no se ve en la factura.
   */
  async updateLogo(id: string, logo: string | null, user_id?: string) {
    const sucursal = await this.prisma.empresas_sucursales.findUnique({
      where: { id },
      select: { id: true, empresa_id: true, punto_establecimiento: true, logo: true },
    });
    if (!sucursal) throw new NotFoundException('Sucursal no encontrada');

    const actualizada = await this.prisma.empresas_sucursales.update({
      where: { id },
      data: { logo, updated_at: new Date() },
      select: { id: true, punto_establecimiento: true, descripcion: true, logo: true },
    });

    if (sucursal.empresa_id) {
      await this.queuesService.enqueueMiddlewareEmpresa(sucursal.empresa_id, 'M', 'empresas');
    }

    await this.auditService.log({
      empresa_id: sucursal.empresa_id ?? undefined,
      user_id,
      action: 'UPDATE',
      entity_type: 'sucursal',
      entity_id: id,
      descripcion: logo
        ? `Logo de sucursal actualizado: ${sucursal.punto_establecimiento ?? id}`
        : `Logo de sucursal eliminado: ${sucursal.punto_establecimiento ?? id}`,
      old_value: { logo: sucursal.logo },
      new_value: { logo },
    });

    return {
      message: logo ? 'Logo actualizado exitosamente' : 'Logo eliminado exitosamente',
      data: actualizada,
    };
  }

  async updateConfig(id: string, config: Record<string, unknown>) {
    const sucursal = await this.prisma.empresas_sucursales.findUnique({
      where: { id },
    });
    if (!sucursal) throw new NotFoundException('Sucursal no encontrada');

    // Merge con config existente
    const currentConfig = (sucursal.config as Record<string, unknown>) || {};
    const newConfig = { ...currentConfig, ...config };

    const updated = await this.prisma.empresas_sucursales.update({
      where: { id },
      data: { config: newConfig as object },
    });

    return { message: 'Configuración actualizada exitosamente', data: updated };
  }

  async getConfig(id: string) {
    const sucursal = await this.prisma.empresas_sucursales.findUnique({
      where: { id },
      select: { id: true, config: true },
    });
    if (!sucursal) throw new NotFoundException('Sucursal no encontrada');

    // Valores por defecto
    const defaultConfig = {
      envio_lote_middleware: true,
      politica_stock: 'advertencia', // 'estricto' | 'advertencia' | 'libre'
      requiere_caja_pagos_proveedor: false,
      maneja_lotes: false,
      metodo_costeo: 'FIFO',
      alertar_lotes_vencidos: true,
      dias_alerta_vencimiento: 30,
      compras_credito_requiere_cuotas: false,
    };

    const config = {
      ...defaultConfig,
      ...((sucursal.config as Record<string, unknown>) || {}),
    };

    return {
      message: 'Configuración obtenida exitosamente',
      data: { ...sucursal, config },
    };
  }
}
