import { useCallback, useEffect, useReducer, useRef } from 'react';

import { apiFetch } from '@/lib/fetch';
import { apiUrl } from '@/lib/url';

import type { BackendMode } from './backend-mode-list';
import type {
  CdpPingResult,
  CloakDoctor,
  CloakLaunchResult,
  CloakRuntimeStatus,
  CloudTestResult,
  DoctorState,
  ExtensionArtifacts,
  ExtensionProbe,
  LaunchedCdpInstance,
  PlaywrightDoctor,
} from './types';

interface ApiEnvelope<T> {
  ok?: boolean;
  payload?: T;
  error?: string | { message?: string };
}

async function getJson<T>(url: string): Promise<T> {
  const res = await apiFetch(url);
  const body = (await res.json().catch(() => ({}))) as ApiEnvelope<T>;
  if (!res.ok || body.ok === false) {
    const msg = typeof body.error === 'string' ? body.error : body.error?.message;
    throw new Error(msg || `Request failed: ${res.status}`);
  }
  if (body.payload === undefined) throw new Error('Empty payload');
  return body.payload;
}

async function postJson<T>(url: string, body: unknown): Promise<T> {
  const res = await apiFetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body ?? {}),
  });
  const json = (await res.json().catch(() => ({}))) as ApiEnvelope<T>;
  if (!res.ok || json.ok === false) {
    const msg = typeof json.error === 'string' ? json.error : json.error?.message;
    throw new Error(msg || `Request failed: ${res.status}`);
  }
  if (json.payload === undefined) throw new Error('Empty payload');
  return json.payload;
}

type BrowserDoctorState = {
  playwright: DoctorState<PlaywrightDoctor>;
  cloak: DoctorState<CloakDoctor>;
  extension: DoctorState<ExtensionProbe>;
  extensionArtifacts: DoctorState<ExtensionArtifacts>;
};

type BrowserDoctorAction =
  | { type: 'playwright'; state: DoctorState<PlaywrightDoctor> }
  | { type: 'cloak'; state: DoctorState<CloakDoctor> }
  | { type: 'extension'; state: DoctorState<ExtensionProbe> }
  | { type: 'extension-artifacts'; state: DoctorState<ExtensionArtifacts> }
  | { type: 'playwright-idle' }
  | { type: 'cloak-idle' }
  | { type: 'extension-idle' }
  | { type: 'extension-start-probe' }
  | { type: 'extension-artifacts-start-probe' };

const initialBrowserDoctorState: BrowserDoctorState = {
  playwright: { kind: 'idle' },
  cloak: { kind: 'idle' },
  extension: { kind: 'idle' },
  extensionArtifacts: { kind: 'idle' },
};

function browserDoctorReducer(state: BrowserDoctorState, action: BrowserDoctorAction): BrowserDoctorState {
  switch (action.type) {
    case 'playwright':
      return { ...state, playwright: action.state };
    case 'cloak':
      return { ...state, cloak: action.state };
    case 'extension':
      return { ...state, extension: action.state };
    case 'extension-artifacts':
      return { ...state, extensionArtifacts: action.state };
    case 'playwright-idle':
      return { ...state, playwright: { kind: 'idle' } };
    case 'cloak-idle':
      return { ...state, cloak: { kind: 'idle' } };
    case 'extension-idle':
      return { ...state, extension: { kind: 'idle' }, extensionArtifacts: { kind: 'idle' } };
    case 'extension-start-probe':
      return {
        ...state,
        extension: state.extension.kind === 'ok' ? state.extension : { kind: 'loading' },
      };
    case 'extension-artifacts-start-probe':
      return {
        ...state,
        extensionArtifacts:
          state.extensionArtifacts.kind === 'ok' ? state.extensionArtifacts : { kind: 'loading' },
      };
  }
}

export interface BrowserDoctor {
  playwright: DoctorState<PlaywrightDoctor>;
  cloak: DoctorState<CloakDoctor>;
  /** Periodic probe of the extension bridge (polled every 5 s when active). */
  extension: DoctorState<ExtensionProbe>;
  extensionArtifacts: DoctorState<ExtensionArtifacts>;
  refetchPlaywright: () => Promise<void>;
  applyPlaywrightDoctor: (data: PlaywrightDoctor) => void;
  refetchCloak: (overrides?: { cacheDir?: string; binaryPath?: string }) => Promise<CloakDoctor | null>;
  applyCloakDoctor: (data: CloakDoctor) => void;
  fetchCloakRuntimeStatus: () => Promise<CloakRuntimeStatus>;
  launchCloak: () => Promise<CloakLaunchResult>;
  pingCdp: (cdpUrl: string) => Promise<CdpPingResult>;
  testCloud: (provider: 'browserbase' | 'browser-use', apiKey: string) => Promise<CloudTestResult>;
  launchCdp: (executablePath?: string) => Promise<LaunchedCdpInstance>;
  stopCdp: (port: number) => Promise<void>;
  listCdpInstances: () => Promise<LaunchedCdpInstance[]>;
  startExtensionBridge: (opts?: { host?: string; port?: number }) => Promise<void>;
  stopExtensionBridge: () => Promise<void>;
  disconnectExtension: () => Promise<void>;
  refetchExtension: () => Promise<void>;
  refetchExtensionArtifacts: () => Promise<ExtensionArtifacts | null>;
  installExtensionArtifacts: (opts?: { force?: boolean }) => Promise<ExtensionArtifacts | null>;
  openExtensionChrome: () => Promise<void>;
  revealExtensionFolder: () => Promise<void>;
}

export function useBrowserDoctor(opts: {
  cacheDir?: string;
  binaryPath?: string;
  /** When false, all backend probes stay idle. */
  browserEnabled?: boolean;
  /** Probe Chrome extension bridge status. */
  probeExtension?: boolean;
  /** Probe local Playwright / Chromium. */
  probeLocal?: boolean;
  /** Probe CloakBrowser install. */
  probeCloak?: boolean;
  /** Active backend — extension bridge auto-start runs only when this is `extension`. */
  activeBackend?: BackendMode;
  extensionHost?: string;
  extensionPort?: number;
}): BrowserDoctor {
  const [doctors, dispatch] = useReducer(browserDoctorReducer, initialBrowserDoctorState);
  const { playwright, cloak, extension, extensionArtifacts } = doctors;

  const refetchPlaywright = useCallback(async () => {
    dispatch({ type: 'playwright', state: { kind: 'loading' } });
    try {
      const data = await getJson<PlaywrightDoctor>(apiUrl('/api/browser/playwright/doctor'));
      dispatch({ type: 'playwright', state: { kind: 'ok', data } });
    } catch (e) {
      dispatch({
        type: 'playwright',
        state: { kind: 'error', message: e instanceof Error ? e.message : String(e) },
      });
    }
  }, []);

  const refetchCloak = useCallback(
    async (overrides?: { cacheDir?: string; binaryPath?: string }) => {
      dispatch({ type: 'cloak', state: { kind: 'loading' } });
      const cd = (overrides?.cacheDir ?? opts.cacheDir ?? '').trim();
      const bp = (overrides?.binaryPath ?? opts.binaryPath ?? '').trim();
      const qs = new URLSearchParams();
      if (cd) qs.set('cacheDir', cd);
      if (bp) qs.set('binaryPath', bp);
      try {
        const data = await getJson<CloakDoctor>(
          apiUrl(`/api/browser/cloakbrowser/doctor${qs.size ? `?${qs}` : ''}`),
        );
        dispatch({ type: 'cloak', state: { kind: 'ok', data } });
        return data;
      } catch (e) {
        dispatch({
          type: 'cloak',
          state: { kind: 'error', message: e instanceof Error ? e.message : String(e) },
        });
        return null;
      }
    },
    [opts.binaryPath, opts.cacheDir],
  );

  const applyPlaywrightDoctor = useCallback((data: PlaywrightDoctor) => {
    dispatch({ type: 'playwright', state: { kind: 'ok', data } });
  }, []);

  const applyCloakDoctor = useCallback((data: CloakDoctor) => {
    dispatch({ type: 'cloak', state: { kind: 'ok', data } });
  }, []);

  const fetchCloakRuntimeStatus = useCallback(async () => {
    return getJson<CloakRuntimeStatus>(apiUrl('/api/browser/cloakbrowser/status'));
  }, []);

  const launchCloak = useCallback(async () => {
    return postJson<CloakLaunchResult>(apiUrl('/api/browser/cloakbrowser/launch'), {});
  }, []);

  const extensionProbe = opts.browserEnabled === true && opts.probeExtension === true;
  const localProbe = opts.browserEnabled === true && opts.probeLocal === true;
  const cloakProbe = opts.browserEnabled === true && opts.probeCloak === true;
  const { extensionHost, extensionPort } = opts;

  const refetchExtension = useCallback(async () => {
    const qs = new URLSearchParams({ probe: '1' });
    if (extensionHost) qs.set('host', extensionHost);
    if (extensionPort !== undefined) qs.set('port', String(extensionPort));
    dispatch({ type: 'extension-start-probe' });
    try {
      const res = await apiFetch(apiUrl(`/api/browser/extension-status?${qs}`), {
        signal: AbortSignal.timeout(3000),
      });
      const data = (await res.json().catch(() => ({}))) as {
        running?: boolean;
        connected?: boolean;
        backend?: string;
        artifacts?: ExtensionArtifacts;
        bridgeHeld?: boolean;
        refCount?: number;
        manualBridge?: boolean;
        portConflict?: boolean;
      };
      if (!res.ok) {
        dispatch({ type: 'extension', state: { kind: 'error', message: `HTTP ${res.status}` } });
        return;
      }
      dispatch({
        type: 'extension',
        state: {
          kind: 'ok',
          data: {
            running: data.running === true,
            connected: data.connected === true,
            backend: data.backend,
            artifacts: data.artifacts,
            bridgeHeld: data.bridgeHeld === true,
            refCount: typeof data.refCount === 'number' ? data.refCount : undefined,
            manualBridge: data.manualBridge === true,
            portConflict: data.portConflict === true,
          },
        },
      });
    } catch (e) {
      dispatch({
        type: 'extension',
        state: { kind: 'error', message: e instanceof Error ? e.message : String(e) },
      });
    }
  }, [extensionHost, extensionPort]);

  const refetchExtensionArtifacts = useCallback(async () => {
    dispatch({ type: 'extension-artifacts-start-probe' });
    try {
      const data = await getJson<ExtensionArtifacts>(apiUrl('/api/browser/extension/doctor'));
      dispatch({ type: 'extension-artifacts', state: { kind: 'ok', data } });
      return data;
    } catch (e) {
      dispatch({
        type: 'extension-artifacts',
        state: { kind: 'error', message: e instanceof Error ? e.message : String(e) },
      });
      return null;
    }
  }, []);

  const installExtensionArtifacts = useCallback(
    async (params?: { force?: boolean }) => {
      const payload = await postJson<{ doctor: ExtensionArtifacts }>(
        apiUrl('/api/browser/extension/install'),
        { force: params?.force ?? false },
      );
      const doctor = payload.doctor;
      dispatch({ type: 'extension-artifacts', state: { kind: 'ok', data: doctor } });
      await refetchExtension();
      return doctor;
    },
    [refetchExtension],
  );

  const openExtensionChrome = useCallback(async () => {
    await postJson<{ extensionDir: string }>(apiUrl('/api/browser/extension/open'), {
      action: 'chrome',
    });
  }, []);

  const revealExtensionFolder = useCallback(async () => {
    await postJson<{ extensionDir: string }>(apiUrl('/api/browser/extension/open'), {
      action: 'folder',
    });
  }, []);

  // Lazy probe per visible settings tab (overview probes all three for the picker).
  useEffect(() => {
    if (!localProbe) {
      dispatch({ type: 'playwright-idle' });
      return undefined;
    }
    void refetchPlaywright();
    return undefined;
  }, [localProbe, refetchPlaywright]);

  useEffect(() => {
    if (!cloakProbe) {
      dispatch({ type: 'cloak-idle' });
      return undefined;
    }
    void refetchCloak();
    return undefined;
  }, [cloakProbe, refetchCloak]);

  const trackedExtensionProbeRef = useRef(extensionProbe);
  if (trackedExtensionProbeRef.current !== extensionProbe) {
    trackedExtensionProbeRef.current = extensionProbe;
    if (!extensionProbe) {
      dispatch({ type: 'extension-idle' });
    }
  }

  useEffect(() => {
    if (!extensionProbe) return undefined;
    void refetchExtension();
    void refetchExtensionArtifacts();
    const id = setInterval(() => void refetchExtension(), 5000);
    return () => clearInterval(id);
  }, [extensionProbe, refetchExtension, refetchExtensionArtifacts]);

  const startExtensionBridge = useCallback(
    async (params?: { host?: string; port?: number }) => {
      await postJson<{ running: boolean }>(apiUrl('/api/browser/extension/start'), {
        host: params?.host ?? extensionHost,
        port: params?.port ?? extensionPort,
      });
      await refetchExtension();
    },
    [extensionHost, extensionPort, refetchExtension],
  );

  const stopExtensionBridge = useCallback(async () => {
    await postJson<{ running: boolean }>(apiUrl('/api/browser/extension/stop'), {});
    await refetchExtension();
  }, [refetchExtension]);

  const disconnectExtension = useCallback(async () => {
    await postJson<{ connected: boolean }>(apiUrl('/api/browser/extension/disconnect'), {});
    await refetchExtension();
  }, [refetchExtension]);

  const extensionAutoStartKeyRef = useRef<string | null>(null);

  useEffect(() => {
    if (!extensionProbe || opts.activeBackend !== 'extension') {
      extensionAutoStartKeyRef.current = null;
      return undefined;
    }
    if (extension.kind !== 'ok') return undefined;
    if (extension.data.running || extension.data.bridgeHeld) return undefined;

    const port = extensionPort ?? 19820;
    const key = `${extensionHost}:${port}`;
    if (extensionAutoStartKeyRef.current === key) return undefined;

    extensionAutoStartKeyRef.current = key;
    void startExtensionBridge({ host: extensionHost, port: extensionPort }).catch(() => {
      extensionAutoStartKeyRef.current = null;
    });
    return undefined;
  }, [extensionProbe, extension, extensionHost, extensionPort, opts.activeBackend, startExtensionBridge]);

  const pingCdp = useCallback(async (cdpUrl: string) => {
    return postJson<CdpPingResult>(apiUrl('/api/browser/cdp/ping'), { cdpUrl });
  }, []);

  const testCloud = useCallback(
    async (provider: 'browserbase' | 'browser-use', apiKey: string) => {
      return postJson<CloudTestResult>(apiUrl('/api/browser/cloud/test-connection'), {
        provider,
        apiKey,
      });
    },
    [],
  );

  const launchCdp = useCallback(async (executablePath?: string) => {
    return postJson<LaunchedCdpInstance>(apiUrl('/api/browser/cdp/launch'), {
      executablePath,
    });
  }, []);

  const stopCdp = useCallback(async (port: number) => {
    await postJson<{ stopped: boolean }>(apiUrl('/api/browser/cdp/stop'), { port });
  }, []);

  const listCdpInstances = useCallback(async () => {
    const payload = await getJson<{ instances: LaunchedCdpInstance[] }>(
      apiUrl('/api/browser/cdp/instances'),
    );
    return payload.instances;
  }, []);

  return {
    playwright,
    cloak,
    extension,
    extensionArtifacts,
    refetchPlaywright,
    applyPlaywrightDoctor,
    refetchCloak,
    applyCloakDoctor,
    fetchCloakRuntimeStatus,
    launchCloak,
    refetchExtension,
    refetchExtensionArtifacts,
    installExtensionArtifacts,
    openExtensionChrome,
    revealExtensionFolder,
    pingCdp,
    testCloud,
    launchCdp,
    stopCdp,
    listCdpInstances,
    startExtensionBridge,
    stopExtensionBridge,
    disconnectExtension,
  };
}
