import AsyncStorage from "@react-native-async-storage/async-storage";
import Constants from "expo-constants";
import * as DevClient from "expo-dev-client";
import * as Device from "expo-device";
import * as SecureStore from "expo-secure-store";
import * as Updates from "expo-updates";
import { useEffect, useState } from "react";
import { Platform } from "react-native";

const LIVE_KEY = "danogo_test_live_v1";
const EVENTS_KEY = "danogo_test_events_v1";
const ISSUES_KEY = "danogo_test_issues_v1";
const SESSION_KEY = "dano_session_token";
const MAX_EVENTS = 200;
const MAX_ISSUES = 50;

export type DanoGoTestEvent = {
  at: string;
  type: string;
  data?: Record<string, unknown>;
};

export type DanoGoIssueEnvelope = {
  id: string;
  createdAt: string;
  projectId?: string;
  projectName?: string;
  revision?: string;
  note: string;
  liveTesting: boolean;
  runtimeVersion: string | null;
  updateId: string | null;
  nativeAppVersion: string | null;
  platform: string;
  osVersion: string | null;
  deviceName: string | null;
  recentEvents: DanoGoTestEvent[];
  uploadState: "queued" | "uploaded";
};

export type DanoGoTestConfig = {
  projectId?: string;
  projectName?: string;
  revision?: string;
  evidenceEndpoint?: string;
};

async function readArray<T>(key: string): Promise<T[]> {
  try {
    const raw = await AsyncStorage.getItem(key);
    const parsed = raw ? JSON.parse(raw) : [];
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

export async function isLiveTestingEnabled(): Promise<boolean> {
  return (await AsyncStorage.getItem(LIVE_KEY)) === "1";
}

export async function setLiveTestingEnabled(enabled: boolean): Promise<void> {
  await AsyncStorage.setItem(LIVE_KEY, enabled ? "1" : "0");
  if (!enabled) {
    await AsyncStorage.removeItem(EVENTS_KEY);
    return;
  }
  await recordTestEvent("live-testing-enabled");
}

export async function recordTestEvent(
  type: string,
  data?: Record<string, unknown>,
): Promise<void> {
  if (!(await isLiveTestingEnabled())) return;
  const events = await readArray<DanoGoTestEvent>(EVENTS_KEY);
  events.push({
    at: new Date().toISOString(),
    type,
    ...(data ? { data } : {}),
  });
  await AsyncStorage.setItem(EVENTS_KEY, JSON.stringify(events.slice(-MAX_EVENTS)));
}

export async function markIssue(
  config: DanoGoTestConfig = {},
  note = "",
): Promise<DanoGoIssueEnvelope> {
  const liveTesting = await isLiveTestingEnabled();
  const recentEvents = liveTesting
    ? (await readArray<DanoGoTestEvent>(EVENTS_KEY)).slice(-MAX_EVENTS)
    : [];

  const issue: DanoGoIssueEnvelope = {
    id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
    createdAt: new Date().toISOString(),
    ...(config.projectId ? { projectId: config.projectId } : {}),
    ...(config.projectName ? { projectName: config.projectName } : {}),
    ...(config.revision ? { revision: config.revision } : {}),
    note: note.trim(),
    liveTesting,
    runtimeVersion: Updates.runtimeVersion ?? null,
    updateId: Updates.updateId ?? null,
    nativeAppVersion: Constants.expoConfig?.version ?? null,
    platform: Platform.OS,
    osVersion: Device.osVersion ?? null,
    deviceName: Device.deviceName ?? Device.modelName ?? null,
    recentEvents,
    uploadState: "queued",
  };

  const queue = await readArray<DanoGoIssueEnvelope>(ISSUES_KEY);
  await AsyncStorage.setItem(
    ISSUES_KEY,
    JSON.stringify([issue, ...queue].slice(0, MAX_ISSUES)),
  );

  if (config.evidenceEndpoint) {
    void flushQueuedIssues(config.evidenceEndpoint);
  }

  return issue;
}

export async function listQueuedIssues(): Promise<DanoGoIssueEnvelope[]> {
  return (await readArray<DanoGoIssueEnvelope>(ISSUES_KEY)).filter(
    (x) => x.uploadState !== "uploaded",
  );
}

export async function flushQueuedIssues(endpoint: string): Promise<number> {
  if (!endpoint.trim()) return 0;
  const queue = await readArray<DanoGoIssueEnvelope>(ISSUES_KEY);
  if (!queue.length) return 0;

  let token: string | null = null;
  try {
    token = await SecureStore.getItemAsync(SESSION_KEY);
  } catch {}

  const remaining: DanoGoIssueEnvelope[] = [];
  let uploaded = 0;

  for (const issue of queue) {
    if (issue.uploadState === "uploaded") continue;
    try {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...(token ? { Authorization: `Bearer ${token}` } : {}),
        },
        body: JSON.stringify(issue),
      });
      if (!response.ok) {
        remaining.push(issue);
        continue;
      }
      uploaded += 1;
    } catch {
      remaining.push(issue);
    }
  }

  await AsyncStorage.setItem(ISSUES_KEY, JSON.stringify(remaining.slice(0, MAX_ISSUES)));
  return uploaded;
}

export function useDanoGoTesting(config: DanoGoTestConfig = {}) {
  const [live, setLive] = useState(false);

  useEffect(() => {
    let cancelled = false;

    const install = async () => {
      const current = await isLiveTestingEnabled();
      if (cancelled) return;
      setLive(current);

      await DevClient.registerDevMenuItems([
        {
          name: "DanoGo: Mark Issue",
          shouldCollapse: true,
          callback: () => {
            void markIssue(config);
          },
        },
        {
          name: `DanoGo: Live Testing ${current ? "ON" : "OFF"}`,
          shouldCollapse: true,
          callback: () => {
            void setLiveTestingEnabled(!current);
          },
        },
        {
          name: "DanoGo: Test Marker",
          shouldCollapse: true,
          callback: () => {
            void recordTestEvent("manual-marker");
          },
        },
        ...(config.evidenceEndpoint
          ? [{
              name: "DanoGo: Send Queued Evidence",
              shouldCollapse: true,
              callback: () => {
                void flushQueuedIssues(config.evidenceEndpoint!);
              },
            }]
          : []),
      ]);
    };

    void install();
    return () => {
      cancelled = true;
    };
  }, [config.evidenceEndpoint, config.projectId, config.projectName, config.revision]);

  const setLiveTesting = async (enabled: boolean) => {
    await setLiveTestingEnabled(enabled);
    setLive(enabled);
  };

  return {
    liveTesting: live,
    setLiveTesting,
    markIssue: (note = "") => markIssue(config, note),
    recordEvent: recordTestEvent,
    flushEvidence: () =>
      config.evidenceEndpoint ? flushQueuedIssues(config.evidenceEndpoint) : Promise.resolve(0),
  };
}
