import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { createHmac, timingSafeEqual } from "crypto";
import { Repository } from "typeorm";
import { Cliente } from "../clientes/entities/cliente.entity";
import { CotizacionesService } from "../cotizaciones/cotizaciones.service";
import { EstadoCotizacion } from "../cotizaciones/entities/cotizacion.entity";
import { EstadoOT, OrdenTrabajo } from "../ots/entities/orden-trabajo.entity";
import { TipoEventoOT } from "../ots/entities/ot-evento.entity";
import { OtsService } from "../ots/ots.service";
import { TallerParametrosService } from "../taller-parametros/taller-parametros.service";
import { TenantRepositoryAccessor } from "../tenancy/tenant-repository.accessor";

type WhatsappRecipient = {
  nombre: string;
  phone: string;
};

@Injectable()
export class WhatsappService {
  constructor(
    private readonly configService: ConfigService,
    private readonly cotizacionesService: CotizacionesService,
    private readonly otsService: OtsService,
    private readonly tenantAccessor: TenantRepositoryAccessor,
    private readonly tallerParametros: TallerParametrosService,
  ) {}

  private get clienteRepository(): Repository<Cliente> {
    return this.tenantAccessor.repositoryFor(Cliente);
  }

  private get otRepository(): Repository<OrdenTrabajo> {
    return this.tenantAccessor.repositoryFor(OrdenTrabajo);
  }

  async sendCotizacion(cotizacionId: number, user: any) {
    const link = await this.cotizacionesService.createPublicLink(
      cotizacionId,
      user?.id,
      user,
    );
    const cotizacion = await this.cotizacionesService.findById(
      cotizacionId,
      user,
    );
    const ot = await this.otsService.findById(cotizacion.otId, user);
    const cliente = await this.findCliente(ot.clienteTarjeta);
    const recipient = await this.resolveRecipient(cliente);
    const publicUrl = this.buildPublicUrl(link.publicPath);
    const total = Number(cotizacion.total || 0).toFixed(2);
    const placa = ot.vehiculo?.placa || "sin placa";
    const mecanico = ot.mecanico?.nombre || "pendiente de asignar";
    const vehiculoInfo = `${placa} | Mecanico: ${mecanico}`;
    const body = [
      `Hola ${recipient.nombre}.`,
      `Tu cotizacion ${cotizacion.numeroCotizacion || cotizacion.id} para el vehiculo ${placa} esta lista.`,
      `Mecanico asignado: ${mecanico}.`,
      `Total: Q ${total}.`,
      `Puedes revisarla, aceptar o rechazar aqui: ${publicUrl}`,
    ].join("\n");

    const response = await this.sendConfiguredMessage({
      to: recipient.phone,
      text: body,
      templateClave: "notificaciones.whatsapp_template_cotizacion",
      parameters: [
        recipient.nombre,
        cotizacion.numeroCotizacion || String(cotizacion.id),
        vehiculoInfo,
        `Q ${total}`,
        publicUrl,
      ],
    });

    await this.otsService.logEvento(
      ot.id,
      TipoEventoOT.OTRO,
      `WhatsApp enviado al cliente con cotizacion ${cotizacion.numeroCotizacion || cotizacion.id}`,
      user?.id,
    );

    return {
      ok: true,
      to: this.maskPhone(recipient.phone),
      publicUrl,
      messageId: response?.messages?.[0]?.id || null,
    };
  }

  async sendEstadoOt(otId: number, user: any) {
    const ot = await this.otsService.findById(otId, user);
    const cliente = await this.findCliente(ot.clienteTarjeta);
    const recipient = await this.resolveRecipient(cliente);
    const statusPath = this.createStatusPath(ot.id);
    const publicUrl = this.buildPublicUrl(statusPath);
    const placa = ot.vehiculo?.placa || "sin placa";
    const estado = this.getEstadoLabel(ot.estado);
    const body = [
      `Hola ${recipient.nombre}.`,
      `Tu vehiculo ${placa} esta en estado: ${estado}.`,
      `Puedes revisar el seguimiento actualizado aqui: ${publicUrl}`,
    ].join("\n");

    const response = await this.sendConfiguredMessage({
      to: recipient.phone,
      text: body,
      templateClave: "notificaciones.whatsapp_template_estado",
      parameters: [recipient.nombre, ot.numeroOt, placa, estado, publicUrl],
    });

    await this.otsService.logEvento(
      ot.id,
      TipoEventoOT.OTRO,
      `WhatsApp enviado al cliente con estado de OT: ${estado}`,
      user?.id,
    );

    return {
      ok: true,
      to: this.maskPhone(recipient.phone),
      publicUrl,
      messageId: response?.messages?.[0]?.id || null,
    };
  }

  async getPublicOtStatus(token: string) {
    const payload = this.verifyStatusToken(token);
    const ot = await this.otRepository.findOne({
      where: { id: payload.otId },
      relations: ["vehiculo", "tienda", "cotizaciones"],
    });

    if (!ot) {
      throw new NotFoundException("Estado no encontrado");
    }

    const cliente = await this.findCliente(ot.clienteTarjeta);
    const cotizacion =
      (ot.cotizaciones || [])
        .slice()
        .sort(
          (a, b) =>
            new Date(b.updatedAt || b.createdAt).getTime() -
            new Date(a.updatedAt || a.createdAt).getTime(),
        )[0] || null;

    return {
      numeroOt: ot.numeroOt,
      estado: ot.estado,
      estadoLabel: this.getEstadoLabel(ot.estado),
      fechaEntrada: ot.fechaEntrada,
      fechaSalida: ot.fechaSalida,
      updatedAt: ot.updatedAt,
      tienda: ot.tienda
        ? {
            id: ot.tienda.id,
            nombre: ot.tienda.nombre,
          }
        : null,
      cliente: {
        nombre: cliente?.nombre || ot.clienteTarjeta,
      },
      vehiculo: ot.vehiculo
        ? {
            placa: ot.vehiculo.placa,
            marca: ot.vehiculo.marca,
            modelo: ot.vehiculo.modelo,
            anio: ot.vehiculo.anio,
          }
        : null,
      cotizacion: cotizacion
        ? {
            numeroCotizacion: cotizacion.numeroCotizacion,
            estado: cotizacion.estado,
            total: Number(cotizacion.total || 0),
            respondida:
              cotizacion.estado === EstadoCotizacion.APROBADA ||
              cotizacion.estado === EstadoCotizacion.RECHAZADA,
          }
        : null,
    };
  }

  private async sendConfiguredMessage({
    to,
    text,
    templateClave,
    parameters,
  }: {
    to: string;
    text: string;
    templateClave:
      | "notificaciones.whatsapp_template_cotizacion"
      | "notificaciones.whatsapp_template_estado";
    parameters: string[];
  }) {
    const usaWhatsapp = await this.tallerParametros.get(
      "notificaciones.usa_whatsapp",
    );
    if (!usaWhatsapp) {
      throw new BadRequestException(
        "WhatsApp no está activado para este tenant -- activalo en Configuración → Notificaciones.",
      );
    }

    const templateName = await this.tallerParametros.get(templateClave);
    if (templateName?.trim()) {
      return this.sendTemplate(to, templateName.trim(), parameters);
    }

    return this.sendText(to, text);
  }

  private async sendText(to: string, body: string) {
    return this.sendGraphMessage({
      messaging_product: "whatsapp",
      recipient_type: "individual",
      to,
      type: "text",
      text: {
        preview_url: true,
        body,
      },
    });
  }

  private async sendTemplate(
    to: string,
    templateName: string,
    parameters: string[],
  ) {
    const language = await this.tallerParametros.get(
      "notificaciones.whatsapp_template_language",
    );
    const template: any = {
      name: templateName,
      language: { code: language },
    };

    if (templateName !== "hello_world") {
      template.components = [
        {
          type: "body",
          parameters: parameters.map((value) => ({
            type: "text",
            text: String(value || "-").slice(0, 1024),
          })),
        },
      ];
    }

    return this.sendGraphMessage({
      messaging_product: "whatsapp",
      to,
      type: "template",
      template,
    });
  }

  private async sendGraphMessage(payload: Record<string, any>) {
    const accessToken = await this.tallerParametros.get(
      "notificaciones.whatsapp_access_token",
    );
    const phoneNumberId = await this.tallerParametros.get(
      "notificaciones.whatsapp_phone_number_id",
    );
    const version = await this.tallerParametros.get(
      "notificaciones.whatsapp_api_version",
    );

    if (!accessToken || !phoneNumberId) {
      throw new BadRequestException(
        "WhatsApp no está configurado para este tenant -- completá Phone Number ID y Access Token en Configuración → Notificaciones.",
      );
    }

    const response = await fetch(
      `https://graph.facebook.com/${version}/${phoneNumberId}/messages`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${accessToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      },
    );

    const text = await response.text();
    let data: any = {};
    try {
      data = text ? JSON.parse(text) : {};
    } catch {
      data = {};
    }

    if (!response.ok) {
      throw new BadRequestException(this.getGraphErrorMessage(data?.error));
    }

    return data;
  }

  private getGraphErrorMessage(error: any) {
    if (!error) return "No se pudo enviar el WhatsApp";

    if (error.code === 190) {
      return "El token de WhatsApp esta vencido o no es valido. Genera un token nuevo en Meta y reinicia el backend.";
    }

    if (error.code === 131030) {
      return "El numero del cliente no esta autorizado para pruebas. Agregalo como destinatario en Meta o usa un numero de WhatsApp Business en produccion.";
    }

    if (error.code === 131047) {
      return "WhatsApp no permite enviar texto libre fuera de la ventana de 24 horas. Configura una plantilla aprobada en Meta.";
    }

    return error.message || "No se pudo enviar el WhatsApp";
  }

  private async findCliente(tarjeta: string) {
    return this.clienteRepository.findOne({ where: { tarjeta } });
  }

  private async resolveRecipient(
    cliente: Cliente | null,
  ): Promise<WhatsappRecipient> {
    const nombre = cliente?.nombre?.trim() || "cliente";
    const phone = await this.normalizePhone(
      cliente?.celular || cliente?.telefono,
    );

    if (!phone) {
      throw new BadRequestException(
        "El cliente no tiene celular o telefono valido para WhatsApp",
      );
    }

    return { nombre, phone };
  }

  private async normalizePhone(value?: string | null): Promise<string> {
    const digits = String(value || "").replace(/\D/g, "");
    if (!digits) return "";
    if (digits.length === 8) {
      const countryCode = await this.tallerParametros.get(
        "notificaciones.whatsapp_default_country_code",
      );
      return `${countryCode}${digits}`;
    }
    if (digits.startsWith("00")) {
      return digits.slice(2);
    }
    return digits;
  }

  private buildPublicUrl(path: string) {
    const base = this.configService.get<string>(
      "PUBLIC_APP_URL",
      "http://localhost:4000",
    );
    return `${base.replace(/\/$/, "")}${path}`;
  }

  private createStatusPath(otId: number) {
    const expiresAt = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60;
    const payload = this.base64UrlEncode(
      JSON.stringify({ otId, exp: expiresAt }),
    );
    const signature = this.sign(payload);
    // El tenant va embebido ADELANTE del token firmado ("<tenantId>.<payload>.<signature>")
    // — PublicTenantGuard lo saca antes de que verifyStatusToken() reciba el
    // resto, que sigue exactamente con la misma forma payload.signature de
    // siempre. Ver tenancy/public-tenant.guard.ts.
    const tenantId = this.tenantAccessor.getCurrentTenantId();
    return `/estado/ot/${tenantId}.${payload}.${signature}`;
  }

  private verifyStatusToken(token: string): { otId: number; exp: number } {
    const normalizedToken = String(token || "").trim();
    if (
      normalizedToken.length > 512 ||
      !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(normalizedToken)
    ) {
      throw new NotFoundException("Estado no encontrado");
    }

    const [payload, signature] = normalizedToken.split(".");
    if (
      !payload ||
      !signature ||
      !this.safeCompare(signature, this.sign(payload))
    ) {
      throw new NotFoundException("Estado no encontrado");
    }

    let parsed: any;
    try {
      parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
    } catch {
      throw new NotFoundException("Estado no encontrado");
    }

    if (!Number.isInteger(parsed.otId) || !Number.isInteger(parsed.exp)) {
      throw new NotFoundException("Estado no encontrado");
    }

    if (parsed.exp < Math.floor(Date.now() / 1000)) {
      throw new BadRequestException("El link de estado expiró");
    }

    return parsed;
  }

  private sign(payload: string) {
    const secret = this.getSigningSecret();
    return createHmac("sha256", secret).update(payload).digest("base64url");
  }

  private getSigningSecret() {
    const secret = this.configService.get<string>("JWT_SECRET", "");
    if (
      process.env.NODE_ENV === "production" &&
      (!secret || secret === "mbtaller" || secret.length < 32)
    ) {
      throw new BadRequestException(
        "Configuracion segura incompleta. Define JWT_SECRET con al menos 32 caracteres.",
      );
    }

    return secret || "mbtaller";
  }

  private safeCompare(a: string, b: string) {
    const left = Buffer.from(a);
    const right = Buffer.from(b);
    return left.length === right.length && timingSafeEqual(left, right);
  }

  private base64UrlEncode(value: string) {
    return Buffer.from(value, "utf8").toString("base64url");
  }

  private maskPhone(phone: string) {
    if (phone.length <= 4) return "****";
    return `${"*".repeat(Math.max(0, phone.length - 4))}${phone.slice(-4)}`;
  }

  private getEstadoLabel(estado: EstadoOT) {
    const labels: Record<EstadoOT, string> = {
      [EstadoOT.BORRADOR]: "Recepcion creada",
      [EstadoOT.EN_PROCESO]: "En proceso",
      [EstadoOT.COTIZADA]: "Cotizada",
      [EstadoOT.APROBADA]: "Aprobada",
      [EstadoOT.TERMINADA]: "Terminada",
      [EstadoOT.FACTURADA]: "Facturada",
      [EstadoOT.ENTREGADA]: "Entregada",
    };

    return labels[estado] || estado;
  }
}
