import {
  HttpErrorResponse,
  HttpEvent,
  HttpHeaders,
  HttpInterceptorFn,
  HttpResponse,
} from '@angular/common/http';
import { inject, isDevMode } from '@angular/core';
import { tap } from 'rxjs';

import { httpErrorMessage } from './http-error.util';
import { DevLogService } from '../devtools/dev-log.service';

function headersToObject(headers: HttpHeaders): Record<string, string | null> {
  const out: Record<string, string | null> = {};
  for (const key of headers.keys()) {
    out[key] = headers.get(key);
  }
  return out;
}

function normalizeBody(value: unknown): unknown {
  if (value === undefined) {
    return null;
  }
  if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
    return value;
  }
  if (value instanceof Blob) {
    return `[Blob ${value.type || 'unknown'} ${value.size} bytes]`;
  }
  if (value instanceof FormData) {
    const fields: Record<string, unknown> = {};
    value.forEach((v, k) => {
      fields[k] = typeof v === 'string' ? v : `[File ${(v as File).name}]`;
    });
    return fields;
  }
  if (value instanceof ArrayBuffer) {
    return `[ArrayBuffer ${value.byteLength} bytes]`;
  }
  return value;
}

export const devLogInterceptor: HttpInterceptorFn = (req, next) => {
  if (!isDevMode()) {
    return next(req);
  }

  const logs = inject(DevLogService);
  const started = performance.now();
  logs.add({
    level: 'info',
    source: 'http',
    status: 'pending',
    message: `→ ${req.method} ${req.urlWithParams} (pending)`,
    details: {
      method: req.method,
      url: req.urlWithParams,
      requestHeaders: headersToObject(req.headers),
      requestBody: normalizeBody(req.body),
    },
  });
  const entryId = logs.entries().at(-1)?.id;

  return next(req).pipe(
    tap({
      next: (event: HttpEvent<unknown>) => {
        if (!(event instanceof HttpResponse)) {
          return;
        }
        const ms = Math.round(performance.now() - started);
        if (entryId) {
          logs.update(entryId, {
            level: 'info',
            status: 'ok',
            message: `✓ ${req.method} ${req.urlWithParams} [${event.status}] ${ms}ms`,
            details: {
              method: req.method,
              url: req.urlWithParams,
              requestHeaders: headersToObject(req.headers),
              requestBody: normalizeBody(req.body),
              statusCode: event.status,
              durationMs: ms,
              responseHeaders: headersToObject(event.headers),
              responseBody: normalizeBody(event.body),
            },
          });
        }
      },
      error: (error: unknown) => {
        const ms = Math.round(performance.now() - started);
        const status = error instanceof HttpErrorResponse ? error.status : undefined;
        if (entryId) {
          logs.update(entryId, {
            level: 'error',
            status: 'error',
            message: `✕ ${req.method} ${req.urlWithParams}${status ? ` [${status}]` : ''} ${ms}ms: ${httpErrorMessage(error)}`,
            details: {
              method: req.method,
              url: req.urlWithParams,
              requestHeaders: headersToObject(req.headers),
              requestBody: normalizeBody(req.body),
              statusCode: status,
              durationMs: ms,
              responseHeaders:
                error instanceof HttpErrorResponse ? headersToObject(error.headers) : undefined,
              responseBody:
                error instanceof HttpErrorResponse ? normalizeBody(error.error) : undefined,
              error: httpErrorMessage(error),
            },
          });
        }
      },
    }),
  );
};
