import { HttpService } from '@nestjs/axios';
import {
  BadRequestException,
  forwardRef,
  Inject,
  Injectable,
  InternalServerErrorException,
  Logger,
} from '@nestjs/common';
import { firstValueFrom } from 'rxjs';
import { OdooInvoiceInput, OdooInvoiceResponse, OdooWebhook } from './entities/odoo.entity';
import { InvoicesService } from '../invoices/invoices.service';
import { randomUUID } from 'crypto';
import type { AxiosError } from 'axios';

@Injectable()
export class OdooService {
  private readonly logger = new Logger(OdooService.name);
  private readonly url: string;
  private readonly httpTimeoutMs: number;

  constructor(
    private readonly httpService: HttpService,
    @Inject(forwardRef(() => InvoicesService)) // 👈 rompe el ciclo
    private readonly invoicesService: InvoicesService,
  ) {
    const url = process.env.URL_ODOO;
    if (!url) {
      this.logger.error('Odoo URL is not defined in environment variables');
      throw new Error('Odoo URL is not defined');
    }
    this.url = url;
    this.httpTimeoutMs = Number(process.env.ODOO_HTTP_TIMEOUT ?? 10_000);

    this.logger.log(`Server Odoo URL: ${this.url}`);
  }

  /** Crea una factura en Odoo con reintentos, logs y validación mínima */
  async createInvoice(
    data: OdooInvoiceInput,
    opts?: { correlationId?: string; retries?: number; timeoutMs?: number },
  ): Promise<OdooInvoiceResponse> {
    const correlationId = opts?.correlationId ?? randomUUID();
    const retries = Math.max(0, opts?.retries ?? 1);
    const timeoutMs = opts?.timeoutMs ?? this.httpTimeoutMs;
    //NOTE PATH ENDPOINT FACTURAS
    const endpoint = `${this.url}/account/move/create`;
    const startedAt = Date.now();

    this.logger.log(
      `[${correlationId}] ➜ POST ${endpoint} | user=${data?.user?.id ?? '—'} amount=${data?.product?.amount ?? '—'}`,
    );

    let lastErr: unknown;

    for (let attempt = 0; attempt <= retries; attempt++) {
      const attemptStr = `${attempt + 1}/${retries + 1}`;

      try {
        const { data: invoice } = await firstValueFrom(
          this.httpService.post<OdooInvoiceResponse>(endpoint, data, {
            timeout: timeoutMs,
            headers: {
              'x-correlation-id': correlationId,
              'content-type': 'application/json',

            },
          }),
        );

        if (!invoice || typeof invoice !== 'object') {
          throw new Error('Respuesta vacía o inválida de Odoo');
        }

        //         {
        //   jsonrpc: "2.0",
        //   id: null,
        //   result: {
        //     invoiceId: "cmgiqfqhn0002wfpc11g7xdfj",
        //     base64Invoice: "[encoded data, 63596 characters]",
        //     linkPayment: "url_de_mercadopago_o_payu",
        //   },
        // }
        if (!invoice.result.invoiceId) {
          throw new Error('Odoo no devolvió id de factura');
        }

        const duration = Date.now() - startedAt;
        this.logger.log(
          `[${correlationId}] ✓ Invoice creada (attempt ${attemptStr}) en ${duration}ms | id=${invoice.result.invoiceId}`,
        );
        return invoice;
      } catch (error) {
        lastErr = error;
        const ax = error as AxiosError;
        const status = ax?.response?.status;
        const code = ax?.code;
        const resp = ax?.response?.data;
        const truncated = this.truncateSafe(resp);

        this.logger.error(
          `[${correlationId}] ✗ Error creando invoice (attempt ${attemptStr}) code=${code ?? '—'} status=${status ?? '—'} msg=${ax?.message ?? error} resp=${truncated}`,
        );

        const shouldRetry =
          attempt < retries &&
          (
            !ax?.isAxiosError ||                // error no-axios (posible red)
            (status && status >= 500) ||        // 5xx
            code === 'ECONNABORTED' ||
            code === 'ETIMEDOUT' ||
            code === 'ECONNREFUSED' ||
            code === 'ENOTFOUND'
          );

        if (shouldRetry) {
          const backoffMs = 300 * (attempt + 1);
          this.logger.warn(`[${correlationId}] Reintentando en ${backoffMs}ms…`);
          await new Promise((r) => setTimeout(r, backoffMs));
          continue;
        }
        break;
      }
    }

    const duration = Date.now() - startedAt;
    this.logger.error(
      `[${correlationId}] ✗ Falló crear invoice tras ${retries + 1} intento(s) en ${duration}ms | últimoError=${this.truncateSafe((lastErr as any)?.message || String(lastErr))}`,
    );
    throw new InternalServerErrorException('Error al crear la factura en Odoo');
  }

  /** Webhook desde Odoo → idempotente + validación de estado + logs */
  async webhook(data: OdooWebhook, headers?: Record<string, string>) {
    const correlationId = (headers?.['x-correlation-id'] || headers?.['X-Correlation-Id'] || randomUUID()).toString();
    const { invoiceId, status } = data ?? ({} as OdooWebhook);

    this.logger.log(
      `[${correlationId}] 🔔 Webhook recibido invoiceId=${invoiceId ?? '—'} status=${status ?? '—'}`,
    );

    // Validación de payload
    if (!invoiceId) {
      this.logger.error(`[${correlationId}] Webhook inválido: falta invoiceId`);
      throw new BadRequestException('invoiceId requerido');
    }
    if (!this.isAllowedStatus(status)) {
      this.logger.error(`[${correlationId}] Webhook inválido: status no permitido (${status})`);
      throw new BadRequestException('status inválido');
    }

    // Buscar factura local
    const invoice = await this.invoicesService.findById(invoiceId);
    if (!invoice) {
      this.logger.warn(`[${correlationId}] Invoice ${invoiceId} no encontrada`);
      // Podrías devolver 200 para no reintento: return { message: 'ok' };
      throw new InternalServerErrorException('Factura no encontrada');
    }

    // Actualizar estado
    await this.invoicesService.updateStatus(invoiceId, status);
    this.logger.log(`[${correlationId}] ✓ Invoice ${invoiceId} actualizada a ${status}`);

    // TODO: notificar al usuario (WhatsApp, email, push)
    return { message: 'Webhook processed' };
  }

  /**
   * Actualiza el estado de una factura en Odoo (para PAID o CANCELED)
   */
  async updateInvoiceStatus(
    invoiceId: string,
    status: 'PAID' | 'CANCELED',
    opts?: { correlationId?: string; retries?: number; timeoutMs?: number }
  ): Promise<{ success: boolean; message: string }> {
    const correlationId = opts?.correlationId ?? randomUUID();
    const retries = Math.max(0, opts?.retries ?? 1);
    const timeoutMs = opts?.timeoutMs ?? this.httpTimeoutMs;

    // Determinar el endpoint según el estado
    const endpoint = status === 'PAID'
      ? `${this.url}/account/move/paid`
      : `${this.url}/account/move/cancel`

    const startedAt = Date.now();

    this.logger.log(
      `[${correlationId}] → POST ${endpoint} | status=${status}, invoiceId=${invoiceId}, retries=${retries}`,
    );

    let lastError: any = null;

    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        const { data } = await firstValueFrom(
          this.httpService.post(
            endpoint,
            { invoiceId },
            {
              timeout: timeoutMs,
              headers: {
                'Content-Type': 'application/json',
                'X-Correlation-ID': correlationId,
              },
            },
          ),
        );

        const elapsed = Date.now() - startedAt;
        this.logger.log(
          `[${correlationId}] ✓ Odoo responded OK in ${elapsed}ms | attempt=${attempt + 1}/${retries + 1}`,
        );

        return { success: true, message: data?.message || 'Status updated in Odoo' };
      } catch (err: any) {
        lastError = err;
        const elapsed = Date.now() - startedAt;
        const isTimeout = err.code === 'ECONNABORTED' || err.code === 'ETIMEDOUT';
        const statusCode = (err as AxiosError)?.response?.status;

        this.logger.warn(
          `[${correlationId}] ✗ Odoo error (attempt ${attempt + 1}/${retries + 1}) in ${elapsed}ms | ` +
          `status=${statusCode}, code=${err.code}, isTimeout=${isTimeout}`,
        );

        // Si es el último intento, lanzar error
        if (attempt === retries) {
          this.logger.error(
            `[${correlationId}] Failed to update invoice status in Odoo after ${retries + 1} attempts`,
          );
          throw new InternalServerErrorException(
            `Error al actualizar el estado en Odoo: ${err.message}`,
          );
        }

        // Esperar antes del siguiente intento
        await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1)));
      }
    }

    throw new InternalServerErrorException('Error al actualizar el estado en Odoo');
  }

  // ---------------- Helpers ----------------

  private truncateSafe(payload: any, max = 800): string {
    try {
      const str = typeof payload === 'string' ? payload : JSON.stringify(payload);
      return str.length > max ? `${str.slice(0, max)}…(+${str.length - max}b)` : str;
    } catch {
      return '[unserializable]';
    }
  }

  private isAllowedStatus(s: unknown): s is OdooWebhook['status'] {
    return s === 'PENDING' || s === 'PAID' || s === 'CANCELED';
  }
}
