import Constants from 'expo-constants';
import * as Device from 'expo-device';
import { Platform } from 'react-native';
import { useSession } from '../state/session';

const base = (
  (Constants.expoConfig?.extra?.apiBaseUrl as string) ||
  'https://baltique.pl/api/mobile/v1'
).replace(/\/$/, '');

type RequestOptions = {
  method?: string;
  body?: unknown;
  headers?: Record<string, string>;
  form?: FormData;
  idempotencyKey?: string;
  skipRefresh?: boolean;
};

export class ApiError extends Error {
  constructor(
    public status: number,
    message: string,
    public code?: string,
    public details?: unknown,
    public requestId?: string,
  ) {
    super(message);
  }
}

function devicePayload() {
  const state = useSession.getState();
  return {
    uid: state.deviceUid,
    platform: Platform.OS,
    app_version: Constants.expoConfig?.version ?? '1.0.0',
    build_number:
      Platform.OS === 'ios'
        ? Constants.expoConfig?.ios?.buildNumber ?? '1'
        : String(Constants.expoConfig?.android?.versionCode ?? 1),
    os_version: String(Platform.Version),
    manufacturer: Device.manufacturer ?? '',
    model: Device.modelName ?? '',
    locale: state.locale,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'Europe/Warsaw',
  };
}

async function raw(path: string, options: RequestOptions = {}): Promise<any> {
  const state = useSession.getState();
  const headers: Record<string, string> = {
    Accept: 'application/json',
    'X-Baltique-Device': state.deviceUid,
    'X-Baltique-Platform': Platform.OS,
    'X-Baltique-Locale': state.locale,
    'X-Baltique-App-Version': Constants.expoConfig?.version ?? '1.0.0',
    ...options.headers,
  };
  if (state.site?.site_key) headers['X-Baltique-Site'] = state.site.site_key;
  if (state.accessToken) headers.Authorization = `Bearer ${state.accessToken}`;
  if (options.idempotencyKey) headers['Idempotency-Key'] = options.idempotencyKey;

  let body: BodyInit | undefined;
  if (options.form) {
    body = options.form as unknown as BodyInit;
  } else if (options.body !== undefined) {
    headers['Content-Type'] = 'application/json';
    body = JSON.stringify(options.body);
  }

  let response: Response;
  try {
    response = await fetch(`${base}${path}`, {
      method: options.method || 'GET',
      headers,
      body,
    });
  } catch {
    throw new ApiError(0, 'Nie udało się połączyć z serwerem.');
  }

  let payload: any = null;
  try {
    payload = await response.json();
  } catch {
    payload = null;
  }

  if (response.status === 401 && !options.skipRefresh && state.refreshToken) {
    const ok = await refreshToken();
    if (ok) return raw(path, { ...options, skipRefresh: true });
  }

  if (!response.ok) {
    const error = payload?.error ?? payload ?? {};
    throw new ApiError(
      response.status,
      error.message || `HTTP ${response.status}`,
      error.code,
      error.details,
      payload?.request_id,
    );
  }

  return payload?.data ?? payload;
}

export async function refreshToken(): Promise<boolean> {
  const state = useSession.getState();
  if (!state.refreshToken) return false;
  try {
    const response = await fetch(`${base}/auth/refresh`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        'X-Baltique-Device': state.deviceUid,
        'X-Baltique-App-Version': Constants.expoConfig?.version ?? '1.0.0',
      },
      body: JSON.stringify({
        refresh_token: state.refreshToken,
        device_uid: state.deviceUid,
        device: devicePayload(),
      }),
    });
    const payload = await response.json();
    if (!response.ok) throw new Error('refresh_failed');
    const tokens = payload?.data?.tokens ?? payload?.data;
    if (!tokens?.access_token || !tokens?.refresh_token) throw new Error('invalid_tokens');
    await state.setTokens(tokens.access_token, tokens.refresh_token);
    return true;
  } catch {
    await state.clear();
    return false;
  }
}

export const api = {
  login: (email: string, password: string) =>
    raw('/auth/login', {
      method: 'POST',
      body: {
        email,
        password,
        device_uid: useSession.getState().deviceUid,
        device: devicePayload(),
      },
      skipRefresh: true,
    }),
  logout: () => raw('/auth/logout', { method: 'POST' }),
  bootstrap: () => raw('/bootstrap'),
  dashboard: () => raw('/dashboard'),

  workItems: (params = '') => raw(`/work-items${params}`),
  workItem: (id: number) => raw(`/work-items/${id}`),
  updateWorkItem: (id: number, status: string) =>
    raw(`/work-items/${id}`, {
      method: 'PATCH',
      body: { status },
      idempotencyKey: `work-${id}-${status}-${Date.now()}`,
    }),

  patients: (q = '', page = 1) =>
    raw(`/patients?q=${encodeURIComponent(q)}&page=${page}`),
  patient: (id: number) => raw(`/patients/${id}`),
  patientTimeline: (id: number) => raw(`/patients/${id}/timeline`),
  patientDocuments: (id: number) => raw(`/patients/${id}/documents`),
  patientLabs: (id: number) => raw(`/patients/${id}/labs`),

  orDay: (date?: string) =>
    raw(`/or-day${date ? `?date=${encodeURIComponent(date)}` : ''}`),

  threads: () => raw('/communications'),
  messages: async (id: number) => {
    const detail = await raw(`/communications/${id}`);
    return { items: detail?.messages ?? [], thread: detail?.thread ?? null };
  },
  createDraft: (id: number, body: string) =>
    raw(`/communications/${id}/drafts`, {
      method: 'POST',
      body: { body, channel: 'internal' },
      idempotencyKey: `draft-${id}-${Date.now()}`,
    }),

  notifications: () => raw('/notifications'),
  markNotification: (id: number) =>
    raw(`/notifications/${id}/read`, { method: 'POST' }),
  search: (q: string) => raw(`/search?q=${encodeURIComponent(q)}`),

  registerDevice: (payload: any) =>
    raw('/devices/push', {
      method: 'PUT',
      body: {
        token: payload.push_token ?? payload.token,
        provider: payload.push_provider ?? payload.provider ?? 'expo',
        permission: payload.permission ?? 'granted',
      },
    }),
  setSite: (site_key: string) =>
    raw('/sites/context', { method: 'PUT', body: { site_key } }),
  setPreferences: (payload: any) =>
    raw('/me/preferences', { method: 'PATCH', body: payload }),

  uploadIntake: async (uri: string, name: string, mime: string) => {
    const form = new FormData();
    form.append('file', { uri, name, type: mime } as any);
    form.append('platform', Platform.OS);
    return raw('/intake/document', {
      method: 'POST',
      form,
      idempotencyKey: `intake-${Date.now()}`,
    });
  },
  uploadLab: async (
    patientId: number,
    uri: string,
    name: string,
    mime: string,
  ) => {
    const form = new FormData();
    form.append('patient_id', String(patientId));
    form.append('file', { uri, name, type: mime } as any);
    return raw('/labs/upload', {
      method: 'POST',
      form,
      idempotencyKey: `lab-${patientId}-${Date.now()}`,
    });
  },
};
