import type { ComputedRef, Ref } from 'vue'

import type { AuthSession } from '../../types/awards'
import type { AwardsStore } from '../../stores/awards'
import type { HomeClipSubmitContext, HomeDisplayCategory, HomeNominationSubmitContext, HomePreviewPhase, HomeSuccessKind } from './homeLandingTypes'

export function useHomeParticipationSubmitActions(params: {
  store: AwardsStore
  displayCategories: ComputedRef<HomeDisplayCategory[]>
  activeCat: Ref<number>
  previewPhase: Ref<HomePreviewPhase>
  votes: Ref<Record<string, number>>
  savedVoteCount: Ref<number>
  submitted: Ref<boolean>
  submitting: Ref<boolean>
  formError: Ref<string>
  successKind: Ref<null | HomeSuccessKind>
  clipCatIdx: Ref<number>
  clipDsgvo: Ref<boolean>
  ensureViewerSession: () => Promise<AuthSession>
  loadMyParticipation: () => Promise<void>
  fallbackCreatorName: ComputedRef<string>
  clipSubmissionsEnabled: ComputedRef<boolean>
  clipSubmissionDisabledMessage: ComputedRef<string>
}) {
  const {
    store,
    displayCategories,
    activeCat,
    previewPhase,
    votes,
    savedVoteCount,
    submitted,
    submitting,
    formError,
    successKind,
    clipCatIdx,
    clipDsgvo,
    ensureViewerSession,
    loadMyParticipation,
    fallbackCreatorName,
    clipSubmissionsEnabled,
    clipSubmissionDisabledMessage,
  } = params

  function setCat(index: number) {
    activeCat.value = index
  }

  function pickNominee(categoryId: string, index: number) {
    if (votes.value[categoryId] === index) {
      const nextVotes = { ...votes.value }
      delete nextVotes[categoryId]
      votes.value = nextVotes
      return
    }

    votes.value = { ...votes.value, [categoryId]: index }
  }

  async function submitVote() {
    formError.value = ''
    if (submitting.value) return
    if (previewPhase.value !== 'voting') {
      formError.value = 'Das Voting ist in dieser Phase nicht verfügbar.'
      return
    }

    if (Object.keys(votes.value).length === 0) {
      formError.value = 'Bitte wähle mindestens eine Kategorie aus.'
      return
    }

    submitting.value = true
    try {
      const session = await ensureViewerSession()
      const entries = Object.entries(votes.value)
        .map(([categoryId, candidateIndex]) => {
          const category = displayCategories.value.find((item) => item.id === categoryId)
          const candidate = category?.candidates[candidateIndex]
          return category && candidate
            ? { categoryId: Number(category.id), candidateId: candidate.id }
            : null
        })
        .filter((entry: { categoryId: number; candidateId: number } | null): entry is { categoryId: number; candidateId: number } => entry !== null)

      if (entries.length === 0) {
        formError.value = 'Bitte wähle mindestens eine gültige Kategorie aus.'
        return
      }

      await store.submitVote({
        seasonId: store.overview.seasonId,
        twitchUserId: session.twitchUserId,
        entries,
      })
      await loadMyParticipation()
      savedVoteCount.value = entries.length
      submitted.value = true
      successKind.value = 'vote'
    } catch (error) {
      formError.value = error instanceof Error ? error.message : 'Deine Stimme konnte nicht gespeichert werden.'
    } finally {
      submitting.value = false
    }
  }

  function submitReminder() {
    submitted.value = true
    successKind.value = 'show'
  }

  function clipDsgvoChange() {
    clipDsgvo.value = !clipDsgvo.value
  }

  function onClipCatChange(event: Event) {
    const target = event.target as HTMLSelectElement
    clipCatIdx.value = Number.parseInt(target.value || '0', 10) || 0
  }

  async function submitNomination(nominationContext: HomeNominationSubmitContext) {
    formError.value = ''
    if (submitting.value) return
    if (previewPhase.value !== 'nomination') {
      formError.value = 'Nominierungen sind nur während der Nominierungsphase möglich.'
      return
    }

    const entries = nominationContext.entries
      .map((entry) => ({
        categoryIndex: entry.categoryIndex,
        streamUrls: entry.streamUrls.map((url) => url.trim()).filter(Boolean),
      }))
      .filter((entry) => entry.streamUrls.length > 0)

    if (entries.length === 0) {
      formError.value = 'Bitte füge mindestens einen Stream- oder Kanal-Link hinzu.'
      return
    }

    const overLimitEntry = entries.find((entry) => entry.streamUrls.length > nominationLinkLimitFor(displayCategories.value[entry.categoryIndex]))
    if (overLimitEntry) {
      formError.value = `Pro Kategorie sind maximal ${nominationLinkLimitFor(displayCategories.value[overLimitEntry.categoryIndex])} Links erlaubt.`
      return
    }

    const invalidUrl = entries.flatMap((entry) => entry.streamUrls).find((url) => !isHttpUrl(url))
    if (invalidUrl) {
      formError.value = 'Bitte gib einen gültigen http(s)-Link ein.'
      return
    }

    const hasDuplicateInCategory = entries.some((entry) => {
      const normalizedUrls = entry.streamUrls.map((url) => normalizeUrlForCompare(url))
      return new Set(normalizedUrls).size !== normalizedUrls.length
    })
    if (hasDuplicateInCategory) {
      formError.value = 'Doppelte Links innerhalb derselben Kategorie sind nicht erlaubt.'
      return
    }

    if (entries.some((entry) => !displayCategories.value[entry.categoryIndex])) {
      formError.value = 'Bitte wähle eine gültige Kategorie aus.'
      return
    }

    submitting.value = true
    try {
      const session = await ensureViewerSession()
      await Promise.all(entries.map((entry) => {
        const category = displayCategories.value[entry.categoryIndex]
        return store.submitNomination({
          year: store.overview.year,
          categoryGroupName: category.groupName,
          twitchUserId: session.twitchUserId,
          nominations: entry.streamUrls.map((streamUrl) => ({ streamUrl })),
        })
      }))
      await loadMyParticipation()
      submitted.value = true
      successKind.value = 'nomination'
    } catch (error) {
      formError.value = error instanceof Error ? error.message : 'Deine Nominierung konnte nicht gespeichert werden.'
    } finally {
      submitting.value = false
    }
  }

  async function submitClip(clipContext: HomeClipSubmitContext) {
    formError.value = ''
    if (!clipDsgvo.value || submitting.value) return
    if (!clipSubmissionsEnabled.value) {
      formError.value = clipSubmissionDisabledMessage.value || 'Clip-Einreichungen sind aktuell geschlossen.'
      return
    }
    if (previewPhase.value !== 'nomination') {
      formError.value = 'Clip-Einreichungen sind nur während der Nominierungsphase möglich.'
      return
    }

    const url = clipContext.clipUrl.trim()
    if (!url) {
      formError.value = 'Bitte gib einen Clip-Link ein.'
      return
    }

    if (!isHttpUrl(url)) {
      formError.value = 'Bitte gib einen gültigen http(s)-Link ein.'
      return
    }

    submitting.value = true
    try {
      const session = await ensureViewerSession()
      const category = displayCategories.value[clipCatIdx.value]
      const selectedCreator = resolveSelectedCreator(category, clipContext.selectedNomineeQuery)
      if (category?.candidates.length && !selectedCreator) {
        formError.value = 'Bitte wähle einen VTuber aus den Vorschlägen aus.'
        return
      }

      await store.submitClip({
        year: store.overview.year,
        categoryId: category ? Number(category.id) : null,
        candidateId: selectedCreator?.id ?? null,
        twitchUserId: session.twitchUserId,
        clipUrl: url,
        title: clipContext.description || `Clip für ${selectedCreator?.displayName ?? 'die Award-Show'}`,
        creator: selectedCreator?.displayName ?? fallbackCreatorName.value,
      })
      submitted.value = true
      successKind.value = 'clip'
    } catch (error) {
      formError.value = error instanceof Error ? error.message : 'Der Clip konnte nicht gespeichert werden.'
    } finally {
      submitting.value = false
    }
  }

  return {
    setCat,
    pickNominee,
    submitVote,
    submitReminder,
    clipDsgvoChange,
    onClipCatChange,
    submitNomination,
    submitClip,
  }
}

function resolveSelectedCreator(category: HomeDisplayCategory | undefined, query: string) {
  const candidates = category?.candidates ?? []
  const normalizedQuery = normalizeSearchValue(query)
  if (!normalizedQuery) return null

  return candidates.find((candidate) => normalizeSearchValue(candidate.displayName) === normalizedQuery)
    ?? candidates.find((candidate) => normalizeSearchValue(candidate.channelSlug) === normalizedQuery)
    ?? candidates.find((candidate) => normalizeSearchValue(candidate.displayName).includes(normalizedQuery))
    ?? null
}

function nominationLinkLimitFor(category: HomeDisplayCategory | undefined) {
  const rawLimit = category?.maxNomineesPerUser
  const limit = typeof rawLimit === 'number' && Number.isFinite(rawLimit) ? rawLimit : 3
  return Math.max(1, Math.min(10, Math.round(limit)))
}

function normalizeSearchValue(value: string) {
  return value.trim().toLowerCase()
}

function normalizeUrlForCompare(value: string) {
  return value.trim().replace(/\/+$/, '').toLowerCase()
}

function isHttpUrl(value: string) {
  try {
    const url = new URL(value)
    return url.protocol === 'http:' || url.protocol === 'https:'
  } catch {
    return false
  }
}
