import {
  useMutation,
  useQuery,
  useQueryClient,
  type QueryClient,
} from '@tanstack/vue-query'
import { computed, toValue, type MaybeRefOrGetter } from 'vue'
import { createMutationRequestContext } from '../services/mutationContext'
import { apiClient } from './client'
import { throwApiProblem } from './contracts'
import type { components, paths } from './generated/schema'
import { queryKeys } from './queryClient'

export type OpenClawRunDto = components['schemas']['OpenClawRunDto']
export type OpenClawRunCollectionDto = components['schemas']['OpenClawRunCollectionDto']
export type OpenClawRunHistoryResponse = components['schemas']['OpenClawRunHistoryResponse']
export type OpenClawRunOperationDto = components['schemas']['OpenClawRunOperationDto']
export type StartOpenClawRunRequest = components['schemas']['StartOpenClawRunRequest']
export type OpenClawRunActionRequest = components['schemas']['OpenClawRunActionRequest']
type GeneratedRunQuery = NonNullable<
  paths['/api/v1/openclaw/runs']['get']['parameters']['query']
>

export interface OpenClawRunFilters {
  limit?: number
  cursor?: string | null
  status?: string | null
  taskId?: string | null
  projectId?: string | null
  sessionKey?: string | null
}

export type OpenClawRunAction = 'stop' | 'resume' | 'retry'

function normalizeFilters(filters: OpenClawRunFilters = {}): OpenClawRunFilters {
  return {
    limit: Math.min(Math.max(filters.limit ?? 50, 1), 200),
    cursor: filters.cursor?.trim() || null,
    status: filters.status?.trim() || null,
    taskId: filters.taskId?.trim() || null,
    projectId: filters.projectId?.trim() || null,
    sessionKey: filters.sessionKey?.trim() || null,
  }
}

function toGeneratedQuery(filters: OpenClawRunFilters): GeneratedRunQuery {
  const query: GeneratedRunQuery = { limit: filters.limit }
  if (filters.cursor) query.cursor = filters.cursor
  if (filters.status) query.status = filters.status
  if (filters.taskId) query.taskId = filters.taskId
  if (filters.projectId) query.projectId = filters.projectId
  if (filters.sessionKey) query.sessionKey = filters.sessionKey
  return query
}

export async function fetchOpenClawRuns(
  filters: OpenClawRunFilters = {},
  signal?: AbortSignal,
): Promise<OpenClawRunCollectionDto> {
  const normalized = normalizeFilters(filters)
  const { data, error, response } = await apiClient.GET('/api/v1/openclaw/runs', {
    params: { query: toGeneratedQuery(normalized) },
    signal,
  })
  if (!response.ok || error || !data) {
    await throwApiProblem(response, 'Runs konnten nicht geladen werden')
  }
  return data as OpenClawRunCollectionDto
}

export async function fetchOpenClawRunHistory(
  id: string,
  signal?: AbortSignal,
): Promise<OpenClawRunHistoryResponse> {
  const { data, error, response } = await apiClient.GET(
    '/api/v1/openclaw/runs/{id}/history',
    {
      params: {
        path: { id },
        query: { gatewayLimit: 200 },
      },
      signal,
    },
  )
  if (!response.ok || error || !data) {
    await throwApiProblem(response, 'Run-Verlauf konnte nicht geladen werden')
  }
  return data as OpenClawRunHistoryResponse
}

export async function startOpenClawRun(
  request: StartOpenClawRunRequest,
): Promise<OpenClawRunOperationDto> {
  const context = createMutationRequestContext('openclaw-run-start')
  const { data, error, response } = await apiClient.POST('/api/v1/openclaw/runs', {
    body: request,
    headers: context.headers,
  })
  if (!response.ok || error || !data) {
    await throwApiProblem(response, 'Run konnte nicht gestartet werden')
  }
  return data as OpenClawRunOperationDto
}

async function executeOpenClawRunAction(input: {
  id: string
  action: OpenClawRunAction
  reason?: string
}): Promise<OpenClawRunOperationDto> {
  const context = createMutationRequestContext(`openclaw-run-${input.action}`)
  const options = {
    params: { path: { id: input.id } },
    body: { reason: input.reason?.trim() || null } satisfies OpenClawRunActionRequest,
    headers: context.headers,
  }
  const result = input.action === 'stop'
    ? await apiClient.POST('/api/v1/openclaw/runs/{id}/stop', options)
    : input.action === 'resume'
      ? await apiClient.POST('/api/v1/openclaw/runs/{id}/resume', options)
      : await apiClient.POST('/api/v1/openclaw/runs/{id}/retry', options)
  if (!result.response.ok || result.error || !result.data) {
    await throwApiProblem(result.response, `Run-Aktion ${input.action} ist fehlgeschlagen`)
  }
  return result.data as OpenClawRunOperationDto
}

function runMatchesFilters(run: OpenClawRunDto, filters: OpenClawRunFilters): boolean {
  return (!filters.status || run.status === filters.status)
    && (!filters.taskId || run.taskId === filters.taskId)
    && (!filters.projectId || run.projectId === filters.projectId)
    && (!filters.sessionKey || run.sessionKey === filters.sessionKey)
}

function listFiltersFromKey(queryKey: readonly unknown[]): OpenClawRunFilters | null {
  const value = queryKey[2]
  if (value === undefined) return {}
  if (!value || typeof value !== 'object' || Array.isArray(value)) return null
  return value as OpenClawRunFilters
}

export function applyOpenClawRunOperation(
  client: QueryClient,
  operation: OpenClawRunOperationDto,
): void {
  const changedRuns = [operation.run, operation.resultRun]
    .filter((run): run is OpenClawRunDto => run !== null)

  for (const run of changedRuns) {
    client.setQueryData(queryKeys.openClawRun(run.id), run)
    client.setQueryData<OpenClawRunHistoryResponse>(
      queryKeys.openClawRunHistory(run.id),
      current => current ? { ...current, run } : current,
    )
  }

  for (const [queryKey, current] of client.getQueriesData<OpenClawRunCollectionDto>({
    queryKey: queryKeys.openClawRuns(),
  })) {
    if (!current || !Array.isArray(current.items)) continue
    const filters = listFiltersFromKey(queryKey)
    if (!filters) continue
    const changedIds = new Set(changedRuns.map(run => run.id))
    const items = current.items
      .filter(run => !changedIds.has(run.id))
      .concat(changedRuns.filter(run => runMatchesFilters(run, filters)))
      .sort((left, right) =>
        Date.parse(right.updatedAt) - Date.parse(left.updatedAt),
      )
    client.setQueryData<OpenClawRunCollectionDto>(queryKey, { ...current, items })
  }
}

function invalidateRunQueries(
  client: QueryClient,
  operation: OpenClawRunOperationDto,
): void {
  const ids = new Set(
    [operation.run, operation.resultRun]
      .filter((run): run is OpenClawRunDto => run !== null)
      .map(run => run.id),
  )
  void client.invalidateQueries({
    predicate: query => {
      const key = query.queryKey
      if (key[0] !== 'openclaw' || key[1] !== 'runs') return false
      const isList = key.length === 2
        || (typeof key[2] === 'object' && key[2] !== null)
      return isList
        || (
          (key[2] === 'detail' || key[2] === 'history')
          && typeof key[3] === 'string'
          && ids.has(key[3])
        )
    },
  })
}

export function useOpenClawRuns(
  filters: MaybeRefOrGetter<OpenClawRunFilters> = {},
) {
  const normalized = computed(() => normalizeFilters(toValue(filters)))
  return useQuery({
    queryKey: computed(() => queryKeys.openClawRuns(normalized.value)),
    queryFn: ({ signal }) => fetchOpenClawRuns(normalized.value, signal),
    placeholderData: previous => previous,
  })
}

export function useOpenClawRunHistory(id: MaybeRefOrGetter<string>) {
  const resolvedId = computed(() => toValue(id))
  return useQuery({
    queryKey: computed(() => queryKeys.openClawRunHistory(resolvedId.value)),
    queryFn: ({ signal }) => fetchOpenClawRunHistory(resolvedId.value, signal),
    enabled: computed(() => resolvedId.value.length > 0),
    placeholderData: (previous, previousQuery) => {
      const previousId = previousQuery?.queryKey.at(-1)
      return previousId === resolvedId.value ? previous : undefined
    },
  })
}

export function useOpenClawRunMutations() {
  const client = useQueryClient()
  const onSuccess = (operation: OpenClawRunOperationDto) => {
    applyOpenClawRunOperation(client, operation)
    invalidateRunQueries(client, operation)
  }
  const startMutation = useMutation({
    mutationFn: startOpenClawRun,
    onSuccess,
  })
  const actionMutation = useMutation({
    mutationFn: executeOpenClawRunAction,
    onSuccess,
  })
  return { startMutation, actionMutation }
}
