import { computed, toValue, watch, type MaybeRefOrGetter } from 'vue'
import {
  useInfiniteQuery,
  useMutation,
  useQueryClient,
  type InfiniteData,
} from '@tanstack/vue-query'
import type { components } from './generated/schema'
import { apiClient } from './client'
import { queryClient, queryKeys } from './queryClient'
import { apiFetch } from '../services/api'
import { markPerformance, measurePerformance } from '../services/browserTelemetry'
import { throwApiProblem } from './contracts'
import { reportOperationResult } from '../services/operationResults'

export type TaskBoardCardDto = components['schemas']['TaskBoardCardDto']
export type TaskBoardPageDto = components['schemas']['TaskBoardPageDto']

export interface TaskBoardColumns {
  offen: TaskBoardCardDto[]
  inProgress: TaskBoardCardDto[]
  review: TaskBoardCardDto[]
  blocked: TaskBoardCardDto[]
  done: TaskBoardCardDto[]
}

const EMPTY_BOARD: TaskBoardColumns = {
  offen: [],
  inProgress: [],
  review: [],
  blocked: [],
  done: [],
}

function uniqueTasks(tasks: TaskBoardCardDto[]): TaskBoardCardDto[] {
  const byId = new Map<string, TaskBoardCardDto>()
  for (const task of tasks) byId.set(task.id, task)
  return [...byId.values()]
}

async function fetchTaskBoard(doneLimit: number, doneCursor: string | null, signal: AbortSignal): Promise<TaskBoardPageDto> {
  const { data, error, response } = await apiClient.GET('/api/v1/tasks/board', {
    params: {
      query: {
        doneLimit,
        ...(doneCursor ? { doneCursor } : {}),
      },
    },
    signal,
  })
  if (!response.ok || error || !data) {
    await throwApiProblem(response, 'Task Board konnte nicht geladen werden')
  }
  return data as TaskBoardPageDto
}

export async function fetchTaskBoardCard(
  id: string,
  signal?: AbortSignal,
): Promise<TaskBoardCardDto | null> {
  const { data, response } = await apiClient.GET('/api/v1/tasks/{id}/board-card', {
    params: { path: { id } },
    signal,
  })
  if (response.status === 404) return null
  if (!response.ok || !data) {
    await throwApiProblem(response, 'Task-Delta konnte nicht geladen werden')
  }
  return data as TaskBoardCardDto
}

async function mutateTaskState(id: string, state: string): Promise<string> {
  const response = await apiFetch(`/api/dashboard/tasks/${encodeURIComponent(id)}/move`, {
    method: 'PATCH',
    body: JSON.stringify({ state }),
  })
  if (!response.ok) await throwApiProblem(response, 'Task-Status konnte nicht geändert werden')
  const updated = await response.json() as components['schemas']['DashboardTaskDto']
  reportOperationResult(updated.operation, 'Task verschoben')
  return id
}

async function createTask(input: {
  title: string
  detail?: string | null
  priority?: string
  assignedTo?: string
}): Promise<string> {
  const response = await apiFetch('/api/dashboard/tasks', {
    method: 'POST',
    body: JSON.stringify({
      title: input.title,
      detail: input.detail ?? null,
      priority: input.priority ?? 'Medium',
      assignedTo: input.assignedTo ?? 'bao',
      source: 'bao',
    }),
  })
  if (!response.ok) await throwApiProblem(response, 'Aufgabe konnte nicht erstellt werden')
  const created = await response.json() as components['schemas']['DashboardTaskDto']
  reportOperationResult(created.operation, 'Task erstellt')
  if (!created.id) throw new Error('Die erstellte Aufgabe enthält keine ID')
  return created.id
}

async function updateTask(id: string, input: {
  title?: string
  detail?: string | null
  priority?: string
  assignedTo?: string | null
  dueDate?: string | null
}): Promise<string> {
  const response = await apiFetch(`/api/dashboard/tasks/${encodeURIComponent(id)}`, {
    method: 'PUT',
    body: JSON.stringify(input),
  })
  if (!response.ok) await throwApiProblem(response, 'Aufgabe konnte nicht gespeichert werden')
  const updated = await response.json() as components['schemas']['DashboardTaskDto']
  reportOperationResult(updated.operation, 'Task aktualisiert')
  return id
}

function canonicalState(state: string): string {
  const states: Record<string, string> = {
    offen: 'Backlog',
    inProgress: 'In progress',
    review: 'Review',
    blocked: 'Blocked',
    done: 'Done',
  }
  return states[state] ?? state
}

function targetColumn(state: string): keyof TaskBoardColumns {
  const canonical = canonicalState(state).toLowerCase()
  if (canonical === 'in progress') return 'inProgress'
  if (canonical === 'review') return 'review'
  if (canonical === 'blocked') return 'blocked'
  if (canonical === 'done') return 'done'
  return 'offen'
}

type TaskBoardInfiniteData = InfiniteData<TaskBoardPageDto, string | null>

function copyWithoutTask(page: TaskBoardPageDto, id: string): TaskBoardPageDto {
  return {
    ...page,
    offen: page.offen.filter(task => task.id !== id),
    inProgress: page.inProgress.filter(task => task.id !== id),
    review: page.review.filter(task => task.id !== id),
    blocked: page.blocked.filter(task => task.id !== id),
    done: page.done.filter(task => task.id !== id),
  }
}

export function removeTaskBoardCardDelta(id: string): void {
  queryClient.setQueriesData<TaskBoardInfiniteData>(
    { queryKey: ['tasks', 'board'] },
    current => current
      ? { ...current, pages: current.pages.map(page => copyWithoutTask(page, id)) }
      : current,
  )
}

export function applyTaskBoardCardDelta(card: TaskBoardCardDto): void {
  queryClient.setQueriesData<TaskBoardInfiniteData>(
    { queryKey: ['tasks', 'board'] },
    current => {
      if (!current?.pages.length) return current
      const pages = current.pages.map(page => copyWithoutTask(page, card.id))
      const first = { ...pages[0] }
      const column = targetColumn(card.state)
      const nextColumn = [card, ...first[column]]
        .sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))
      first[column] = nextColumn
      pages[0] = first
      return { ...current, pages }
    },
  )
}

const taskDeltaRequests = new Map<string, Promise<void>>()

export function reconcileTaskBoardCard(id: string): Promise<void> {
  const existing = taskDeltaRequests.get(id)
  if (existing) return existing

  const request = fetchTaskBoardCard(id)
    .then(card => {
      if (card) applyTaskBoardCardDelta(card)
      else removeTaskBoardCardDelta(id)
    })
    .catch(() => queryClient.invalidateQueries({ queryKey: ['tasks', 'board'] }))
    .finally(() => taskDeltaRequests.delete(id))
  taskDeltaRequests.set(id, request)
  return request
}

export function useTaskBoard(
  doneLimit = 50,
  enabled: MaybeRefOrGetter<boolean> = true,
) {
  markPerformance('board-navigation')
  const client = useQueryClient()
  const key = queryKeys.taskBoard(doneLimit)
  const query = useInfiniteQuery({
    queryKey: key,
    initialPageParam: null as string | null,
    queryFn: ({ pageParam, signal }) => fetchTaskBoard(doneLimit, pageParam, signal),
    getNextPageParam: lastPage => lastPage.hasMoreDone
      ? lastPage.nextDoneCursor || undefined
      : undefined,
    enabled: () => toValue(enabled),
    staleTime: 15_000,
  })

  const board = computed<TaskBoardColumns>(() => {
    const pages = query.data.value?.pages
    if (!pages?.length) return EMPTY_BOARD
    const first = pages[0]
    return {
      offen: first.offen,
      inProgress: first.inProgress,
      review: first.review,
      blocked: first.blocked,
      done: uniqueTasks(pages.flatMap(page => page.done)),
    }
  })

  const revision = computed(() => query.data.value?.pages[0]?.revision ?? '')

  watch(() => query.isSuccess.value, success => {
    if (!success) return
    requestAnimationFrame(() => {
      markPerformance('board-content-visible')
      void measurePerformance('board_content_visible', 'board-navigation', 'board-content-visible')
    })
  }, { immediate: true })

  const moveMutation = useMutation({
    mutationFn: ({ id, state }: { id: string; state: string }) => mutateTaskState(id, state),
    onMutate: async ({ id, state }) => {
      await client.cancelQueries({ queryKey: key })
      const previous = client.getQueryData(key)
      client.setQueryData(key, (current: typeof query.data.value) => {
        if (!current) return current
        let moved: TaskBoardCardDto | null = null
        const pages = current.pages.map((page, pageIndex) => {
          const copy: TaskBoardPageDto = {
            ...page,
            offen: [...page.offen],
            inProgress: [...page.inProgress],
            review: [...page.review],
            blocked: [...page.blocked],
            done: [...page.done],
          }
          for (const column of ['offen', 'inProgress', 'review', 'blocked', 'done'] as const) {
            const index = copy[column].findIndex(task => task.id === id)
            if (index >= 0) {
              moved = { ...copy[column][index], state: canonicalState(state) }
              copy[column].splice(index, 1)
            }
          }
          if (pageIndex === 0 && moved) copy[targetColumn(state)].push(moved)
          return copy
        })
        return { ...current, pages }
      })
      return { previous }
    },
    onError: (_error, _variables, context) => {
      if (context?.previous) client.setQueryData(key, context.previous)
    },
    onSuccess: id => reconcileTaskBoardCard(id),
  })

  const createMutation = useMutation({
    mutationFn: createTask,
    onSuccess: id => reconcileTaskBoardCard(id),
  })

  const updateMutation = useMutation({
    mutationFn: ({ id, input }: {
      id: string
      input: Parameters<typeof updateTask>[1]
    }) => updateTask(id, input),
    onSuccess: id => reconcileTaskBoardCard(id),
  })

  return {
    query,
    board,
    revision,
    moveTask: (id: string, state: string) => moveMutation.mutateAsync({ id, state }),
    createTask: (input: Parameters<typeof createTask>[0]) => createMutation.mutateAsync(input),
    updateTask: (id: string, input: Parameters<typeof updateTask>[1]) => updateMutation.mutateAsync({ id, input }),
    loadMoreDone: () => query.fetchNextPage(),
    hasMoreDone: computed(() => Boolean(query.hasNextPage.value)),
    loadingMoreDone: computed(() => query.isFetchingNextPage.value),
  }
}
