import { clearAuthSession, readAuthSession, setAuthSession } from './auth-storage';
import { getApiUrl } from './runtime-api';

let refreshPromise: Promise<string | null> | null = null;

interface FetchOptions extends RequestInit {
  token?: string;
  skipAuthRefresh?: boolean;
}

async function parseErrorResponse(response: Response) {
  return response.json().catch(() => ({ message: 'Error de conexion' }));
}

async function refreshAccessToken(): Promise<string | null> {
  if (refreshPromise) {
    return refreshPromise;
  }

  refreshPromise = (async () => {
    const { refreshToken, user } = readAuthSession();

    if (!refreshToken) {
      clearAuthSession({ redirectToLogin: true });
      return null;
    }

    const response = await fetch(`${getApiUrl()}/auth/refresh`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ refreshToken }),
    });

    if (!response.ok) {
      clearAuthSession({ redirectToLogin: true });
      return null;
    }

    const data = await response.json();
    setAuthSession({
      accessToken: data.accessToken,
      refreshToken: data.refreshToken || refreshToken,
      user,
    });

    return data.accessToken || null;
  })().finally(() => {
    refreshPromise = null;
  });

  return refreshPromise;
}

export async function apiFetch<T>(
  endpoint: string,
  options: FetchOptions = {}
): Promise<T> {
  const { token, skipAuthRefresh = false, ...fetchOptions } = options;

  const headers: HeadersInit = {
    'Content-Type': 'application/json',
    ...options.headers,
  };

  if (token) {
    (headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
  } else {
    const storedToken = readAuthSession().accessToken;
    if (storedToken) {
      (headers as Record<string, string>)['Authorization'] = `Bearer ${storedToken}`;
    }
  }

  const response = await fetch(`${getApiUrl()}${endpoint}`, {
    ...fetchOptions,
    headers,
  });

  if (
    response.status === 401 &&
    !skipAuthRefresh &&
    endpoint !== '/auth/login' &&
    endpoint !== '/auth/refresh'
  ) {
    const renewedToken = await refreshAccessToken();

    if (renewedToken) {
      return apiFetch<T>(endpoint, {
        ...options,
        token: renewedToken,
        skipAuthRefresh: true,
      });
    }

    const expiredError: any = new Error('Sesion expirada. Inicia sesion de nuevo.');
    expiredError.status = 401;
    throw expiredError;
  }

  if (!response.ok) {
    const parsedError = await parseErrorResponse(response);
    const error: any = new Error(parsedError.message || `HTTP error ${response.status}`);
    error.status = response.status;
    throw error;
  }

  if (response.status === 204) {
    return {} as T;
  }

  return response.json();
}

export const authApi = {
  login: (username: string, password: string) =>
    apiFetch<{ accessToken: string; refreshToken: string; user: any }>('/auth/login', {
      method: 'POST',
      body: JSON.stringify({ username, password }),
    }),

  refresh: (refreshToken: string) =>
    apiFetch<{ accessToken: string }>('/auth/refresh', {
      method: 'POST',
      body: JSON.stringify({ refreshToken }),
    }),

  me: () => apiFetch<any>('/auth/me'),

  users: () => apiFetch<any[]>('/auth/users'),
  createUser: (data: any) =>
    apiFetch<any>('/auth/users', {
      method: 'POST',
      body: JSON.stringify(data),
    }),
  updateUser: (id: number, data: any) =>
    apiFetch<any>(`/auth/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
  setUserActive: (id: number, value: boolean) =>
    apiFetch<any>(`/auth/users/${id}/activo?value=${value}`, {
      method: 'PATCH',
    }),
};

export const otsApi = {
  list: (params?: Record<string, any>) => {
    const query = params ? `?${new URLSearchParams(params as any)}` : '';
    return apiFetch<any>(`/ots${query}`);
  },

  get: (id: number) => apiFetch<any>(`/ots/${id}`),

  create: (data: any) =>
    apiFetch<any>('/ots', {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  update: (id: number, data: any) =>
    apiFetch<any>(`/ots/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),

  changeEstado: (id: number, estado: string, fechaSalida?: string) =>
    apiFetch<any>(`/ots/${id}/estado`, {
      method: 'PATCH',
      body: JSON.stringify({ estado, fechaSalida }),
    }),

  addDano: (otId: number, data: any) =>
    apiFetch<any>(`/ots/${otId}/danos`, {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  removeDano: (otId: number, danoId: number) =>
    apiFetch<void>(`/ots/${otId}/danos/${danoId}`, {
      method: 'DELETE',
    }),

  addFoto: (otId: number, data: any) =>
    apiFetch<any>(`/ots/${otId}/fotos`, {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  removeFoto: (otId: number, fotoId: number) =>
    apiFetch<void>(`/ots/${otId}/fotos/${fotoId}`, {
      method: 'DELETE',
    }),

  saveFirmas: (otId: number, data: any) =>
    apiFetch<any>(`/ots/${otId}/firmas`, {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  savePdfEscaner: (otId: number, pdfUrl: string) =>
    apiFetch<any>(`/ots/${otId}/pdf`, {
      method: 'POST',
      body: JSON.stringify({ pdfUrl }),
    }),
};
export const clientesApi = {
  list: (params?: Record<string, any>) => {
    const query = params ? `?${new URLSearchParams(params as any)}` : '';
    return apiFetch<any>(`/clientes${query}`);
  },

  search: (q: string) =>
    apiFetch<any[]>(`/clientes/search?q=${encodeURIComponent(q)}`),

  get: (tarjeta: string) => apiFetch<any>(`/clientes/${tarjeta}`),

  create: (data: any) =>
    apiFetch<any>('/clientes', {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  update: (tarjeta: string, data: any) =>
    apiFetch<any>(`/clientes/${tarjeta}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
};

export const vehiculosApi = {
  list: (clienteId?: string) => {
    const query = clienteId ? `?clienteId=${clienteId}` : '';
    return apiFetch<any[]>(`/vehiculos${query}`);
  },

  search: (q: string) =>
    apiFetch<any[]>(`/vehiculos/search?q=${encodeURIComponent(q)}`),

  getByPlaca: (placa: string) => apiFetch<any>(`/vehiculos/placa/${placa}`),

  create: (data: any) =>
    apiFetch<any>('/vehiculos', {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  update: (id: number, data: any) =>
    apiFetch<any>(`/vehiculos/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
};

export const productosApi = {
  search: (
    q: string,
    limit = 20,
    options?: { esServicio?: boolean; soloTaller?: boolean },
  ) => {
    const params = new URLSearchParams({ q, limit: String(limit) });
    if (options?.esServicio !== undefined) {
      params.set('esServicio', String(options.esServicio));
    }
    if (options?.soloTaller !== undefined) {
      params.set('soloTaller', String(options.soloTaller));
    }
    return apiFetch<any[]>(`/productos/search?${params.toString()}`);
  },

  get: (plu: string) => apiFetch<any>(`/productos/${plu}`),

  getTop: (
    limit = 50,
    options?: { esServicio?: boolean; soloTaller?: boolean },
  ) => {
    const params = new URLSearchParams({ limit: String(limit) });
    if (options?.esServicio !== undefined) {
      params.set('esServicio', String(options.esServicio));
    }
    if (options?.soloTaller !== undefined) {
      params.set('soloTaller', String(options.soloTaller));
    }
    return apiFetch<any[]>(`/productos/top?${params.toString()}`);
  },

  create: (data: any) =>
    apiFetch<any>('/productos', {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  update: (plu: string, data: any) =>
    apiFetch<any>(`/productos/${plu}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),

  delete: (plu: string) =>
    apiFetch<void>(`/productos/${plu}`, {
      method: 'DELETE',
    }),
};
export const cotizacionesApi = {
  getByOt: (otId: number) => apiFetch<any>(`/cotizaciones/ot/${otId}`),

  create: (data: any) =>
    apiFetch<any>('/cotizaciones', {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  addLinea: (cotId: number, data: any) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lineas`, {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  updateLinea: (cotId: number, lineaId: number, data: any) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lineas/${lineaId}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),

  removeLinea: (cotId: number, lineaId: number) =>
    apiFetch<void>(`/cotizaciones/${cotId}/lineas/${lineaId}`, {
      method: 'DELETE',
    }),

  aprobar: (cotId: number) =>
    apiFetch<any>(`/cotizaciones/${cotId}/aprobar`, {
      method: 'PATCH',
    }),
};

export const catalogosApi = {
  tiposVehiculo: () => apiFetch<any[]>('/catalogos/tipos-vehiculo'),
  createTipoVehiculo: (data: any) =>
    apiFetch<any>('/catalogos/tipos-vehiculo', {
      method: 'POST',
      body: JSON.stringify(data),
    }),
  updateTipoVehiculo: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/tipos-vehiculo/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
  inactivateTipoVehiculo: (id: number) =>
    apiFetch<any>(`/catalogos/tipos-vehiculo/${id}/inactivar`, {
      method: 'PATCH',
    }),

  cosasTrae: () => apiFetch<any[]>('/catalogos/cosas-trae'),
  createCosaTrae: (data: any) =>
    apiFetch<any>('/catalogos/cosas-trae', {
      method: 'POST',
      body: JSON.stringify(data),
    }),
  updateCosaTrae: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/cosas-trae/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
  inactivateCosaTrae: (id: number) =>
    apiFetch<any>(`/catalogos/cosas-trae/${id}/inactivar`, {
      method: 'PATCH',
    }),

  tiposDano: () => apiFetch<any[]>('/catalogos/tipos-dano'),
  createTipoDano: (data: any) =>
    apiFetch<any>('/catalogos/tipos-dano', {
      method: 'POST',
      body: JSON.stringify(data),
    }),
  updateTipoDano: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/tipos-dano/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
  inactivateTipoDano: (id: number) =>
    apiFetch<any>(`/catalogos/tipos-dano/${id}/inactivar`, {
      method: 'PATCH',
    }),

  vehicular: (tipo: 'marca' | 'modelo' | 'motor' | 'cilindraje', filters?: Record<string, any>) => {
    const params = new URLSearchParams({ tipo, ...(filters || {}) } as any);
    return apiFetch<any[]>(`/catalogos/vehicular?${params.toString()}`);
  },
  createVehicular: (data: any) =>
    apiFetch<any>('/catalogos/vehicular', {
      method: 'POST',
      body: JSON.stringify(data),
    }),
  updateVehicular: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/vehicular/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    }),
  inactivateVehicular: (id: number) =>
    apiFetch<any>(`/catalogos/vehicular/${id}/inactivar`, {
      method: 'PATCH',
    }),
};

export const historialApi = {
  byCliente: (tarjeta: string) => apiFetch<any>(`/historial/cliente/${tarjeta}`),
  byPlaca: (placa: string) => apiFetch<any>(`/historial/placa/${placa}`),
};

export const syncApi = {
  push: (events: any[], deviceId?: string) =>
    apiFetch<any>('/sync/push', {
      method: 'POST',
      body: JSON.stringify({ events, deviceId }),
    }),

  pull: (since: number) => apiFetch<any>(`/sync/pull?since=${since}`),
};

export interface UploadStatusSnapshot {
  attempt: number;
  totalAttempts: number;
  phase: 'starting' | 'uploading' | 'retrying' | 'processing' | 'done';
  message: string;
}

interface UploadFileOptions {
  maxRetries?: number;
  timeoutMs?: number;
  onStatusChange?: (status: UploadStatusSnapshot) => void;
}

function wait(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function isRetryableUploadError(error: any): boolean {
  const status = Number(error?.status);
  if (status === 408 || status === 425 || status === 429) {
    return true;
  }

  if (Number.isFinite(status) && status >= 500) {
    return true;
  }

  const code = String(error?.code || '').toUpperCase();
  if (code === 'NETWORK' || code === 'TIMEOUT') {
    return true;
  }

  const message = String(error?.message || '');
  return /network|timeout|failed to fetch|connection|conexion/i.test(message);
}

function buildUploadLabel(type: 'foto' | 'firma' | 'pdf'): string {
  if (type === 'pdf') return 'documento';
  if (type === 'firma') return 'firma';
  return 'archivo';
}

export async function uploadFile(
  file: File,
  type: 'foto' | 'firma' | 'pdf'
): Promise<{ url: string }> {
  return uploadFileWithProgress(file, type);
}

export async function uploadFileWithProgress(
  file: File,
  type: 'foto' | 'firma' | 'pdf',
  onProgress?: (progress: number) => void,
  options: UploadFileOptions = {},
): Promise<{ url: string }> {
  const {
    maxRetries = 2,
    timeoutMs = 120000,
    onStatusChange,
  } = options;

  let token = readAuthSession().accessToken;
  let authRefreshed = false;
  const totalAttempts = Math.max(1, maxRetries + 1);
  const uploadLabel = buildUploadLabel(type);

  const emitStatus = (status: UploadStatusSnapshot) => {
    onStatusChange?.(status);
  };

  const sendOnce = (activeToken: string | null, attempt: number) =>
    new Promise<{ url: string }>((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open('POST', `${getApiUrl()}/uploads/${type}`);
      xhr.timeout = timeoutMs;

      if (activeToken) {
        xhr.setRequestHeader('Authorization', `Bearer ${activeToken}`);
      }

      emitStatus({
        attempt,
        totalAttempts,
        phase: attempt === 1 ? 'starting' : 'uploading',
        message:
          attempt === 1
            ? `Preparando ${uploadLabel}...`
            : `Reanudando subida (${attempt}/${totalAttempts})`,
      });

      xhr.upload.onprogress = (event) => {
        if (!event.lengthComputable) return;
        const progress = Math.min(100, Math.round((event.loaded / event.total) * 100));
        onProgress?.(progress);
        emitStatus({
          attempt,
          totalAttempts,
          phase: 'uploading',
          message: `Subiendo ${uploadLabel} (${attempt}/${totalAttempts})`,
        });
      };

      xhr.onerror = () => {
        const error: any = new Error('Error de conexion durante la carga');
        error.code = 'NETWORK';
        reject(error);
      };

      xhr.ontimeout = () => {
        const error: any = new Error('La carga excedio el tiempo de espera');
        error.code = 'TIMEOUT';
        reject(error);
      };

      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          emitStatus({
            attempt,
            totalAttempts,
            phase: 'processing',
            message: 'Guardando respuesta del servidor...',
          });

          try {
            resolve(JSON.parse(xhr.responseText || '{}'));
          } catch {
            reject(new Error('Respuesta invalida del servidor'));
          }
          return;
        }

        if (xhr.status === 401) {
          const error: any = new Error('unauthorized');
          error.status = 401;
          reject(error);
          return;
        }

        try {
          const parsed = JSON.parse(xhr.responseText || '{}');
          const error: any = new Error(parsed.message || 'Error de upload');
          error.status = xhr.status;
          reject(error);
        } catch {
          const error: any = new Error('Error de upload');
          error.status = xhr.status;
          reject(error);
        }
      };

      const formData = new FormData();
      formData.append('file', file);
      xhr.send(formData);
    });

  for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
    try {
      const result = await sendOnce(token, attempt);
      onProgress?.(100);
      emitStatus({
        attempt,
        totalAttempts,
        phase: 'done',
        message: `${uploadLabel.charAt(0).toUpperCase()}${uploadLabel.slice(1)} cargado correctamente`,
      });
      return result;
    } catch (error: any) {
      if (error?.status === 401 && !authRefreshed) {
        token = await refreshAccessToken();
        authRefreshed = true;
        if (!token) {
          throw new Error('Sesion expirada. Inicia sesion de nuevo.');
        }
        attempt -= 1;
        continue;
      }

      const canRetry = attempt < totalAttempts && isRetryableUploadError(error);
      if (!canRetry) {
        throw error;
      }

      emitStatus({
        attempt,
        totalAttempts,
        phase: 'retrying',
        message: `Reintentando carga (${attempt + 1}/${totalAttempts})...`,
      });
      await wait(Math.min(5000, 900 * attempt));
    }
  }

  throw new Error('No se pudo completar la carga.');
}
