import { Feather } from '@expo/vector-icons'
import { Link, router } from 'expo-router'
import { Pressable } from 'react-native'
import { onMentionPressMethod } from 'src/components/common/AutolinkText'
import SaveableMedia from 'src/components/common/SaveableMedia'
import StatusContent from 'src/components/common/StatusContent'
import UserAvatar from 'src/components/common/UserAvatar'
import RsvpPill from 'src/components/event/RsvpPill'
import ImageComponent from 'src/components/ImageComponent'
import { useRsvpMutation } from 'src/hooks/mutations/useRsvpMutation'
import { useEventRsvpAvailable } from 'src/hooks/useEventRsvpAvailable'
import type { RsvpAction } from 'src/lib/api'
import type { Account, PfEvent, PfEventTag, Status } from 'src/lib/api-types'
import {
  deviceTimeZone,
  isEventPast,
  isHydratedAccount,
  normalizeLanguage,
  rawOrganizerHandle,
} from 'src/lib/events'
import { ICON } from 'src/lib/icons'
import { getMediaAspectRatio, toImageUri } from 'src/lib/media-source'
import type {
  MobilizonEventEnrichment,
  MobilizonRelatedEvent,
} from 'src/lib/mobilizon-enrichment'
import {
  getReplyCount,
  openBrowserAsync,
  preprocessStatusContent,
  prettyCount,
  safeOpenOriginUrl,
} from 'src/utils'
import { Separator, Text, useTheme, View, XStack, YStack } from 'tamagui'
import { useEventEnrichment } from '../../hooks/useEventEnrichment'

function hostnameFrom(url: string | null | undefined): string | null {
  if (!url) return null
  try {
    return new URL(url).hostname
  } catch {
    return null
  }
}

type EventDetailViewProps = {
  post: Status
  // When true, this was loaded by URL-paste on a host the user's instance hasn't
  // federated with yet — RSVP buttons stay disabled with a tooltip.
  readOnly?: boolean
  // Phase 4.1a — when embedded inside the merged `/post/[id]` thread view
  // the comments link below the body would route back to the same screen
  // (no-op visual loop), so the screen suppresses it. Standalone surfaces
  // still surface it as the entry into the thread.
  hideCommentsLink?: boolean
}

function formatMultiDayLabel(
  start: string | null | undefined,
  end: string | null | undefined,
  longEvent: boolean,
  timeZone: string | null
): string | null {
  if (!longEvent || !start || !end) return null
  try {
    const endDate = new Date(end)
    if (Number.isNaN(endDate.getTime())) return null
    const fmt = new Intl.DateTimeFormat(undefined, {
      month: 'short',
      day: 'numeric',
      ...(timeZone ? { timeZone } : null),
    })
    return `Multi-day · through ${fmt.format(endDate)}`
  } catch {
    return 'Multi-day event'
  }
}

function formatRange(
  start: string | null | undefined,
  end: string | null | undefined,
  isOpenEnded: boolean,
  timeZone: string | null
) {
  if (!start) return null
  const startDate = new Date(start)
  if (Number.isNaN(startDate.getTime())) return null

  try {
    const zoneOpts = timeZone ? { timeZone } : null
    const fmtDay = new Intl.DateTimeFormat(undefined, {
      weekday: 'long',
      month: 'long',
      day: 'numeric',
      year: 'numeric',
      ...zoneOpts,
    })
    const fmtTime = new Intl.DateTimeFormat(undefined, {
      hour: 'numeric',
      minute: '2-digit',
      ...zoneOpts,
    })
    // Use a zoned comparison for sameDay — comparing `toDateString()` on
    // native Date objects uses the device zone, which would flip the sameDay
    // decision near midnight when the event is in a different timezone.
    const fmtDateKey = new Intl.DateTimeFormat('en-CA', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      ...zoneOpts,
    })
    const startLine = `${fmtDay.format(startDate)} · ${fmtTime.format(startDate)}`

    if (isOpenEnded || !end) return startLine
    const endDate = new Date(end)
    if (Number.isNaN(endDate.getTime())) return startLine

    const sameDay = fmtDateKey.format(startDate) === fmtDateKey.format(endDate)
    if (sameDay) return `${startLine} – ${fmtTime.format(endDate)}`
    return `${startLine} – ${fmtDay.format(endDate)} · ${fmtTime.format(endDate)}`
  } catch {
    return startDate.toString()
  }
}

const ONE_DAY_MS = 24 * 60 * 60 * 1000

function deriveLongEvent(start: string | null, end: string | null): boolean {
  if (!start || !end) return false
  const s = Date.parse(start)
  const e = Date.parse(end)
  if (Number.isNaN(s) || Number.isNaN(e)) return false
  return e - s >= ONE_DAY_MS
}

// Coerce both the server's legacy `{ name, slug }` shape and the hydrated
// `{ title, slug }` shape into a single `{ slug, title }` view, skipping
// empties. Mobilizon enrichment is layered on top of Pixelfed tags; later
// entries for the same slug win.
function mergeTags(
  eventTags: Array<PfEventTag> | undefined,
  mzTags: Array<PfEventTag> | null | undefined
): PfEventTag[] {
  const bySlug = new Map<string, PfEventTag>()
  const ingest = (tags: Array<PfEventTag> | null | undefined) => {
    for (const t of tags ?? []) {
      const title = (t.title ?? (t as unknown as { name?: string }).name ?? '').trim()
      const slug = (t.slug ?? '').trim() || title.toLowerCase().replace(/\s+/g, '-')
      if (!slug || !title) continue
      bySlug.set(slug, { slug, title })
    }
  }
  ingest(eventTags)
  ingest(mzTags)
  return [...bySlug.values()]
}

// Merge rules: title/description/date/cover prefer Mobilizon (origin is the
// source of truth); counts take the max across both; Mobilizon-only fields
// render as additional sections.
function mergeEvent(event: PfEvent, mz: Partial<MobilizonEventEnrichment> | null) {
  const startTime = mz?.beginsOn || event.start_time
  const endTime = mz?.endsOn || event.end_time

  const base = {
    isOpenEnded: event.is_open_ended,
    interested: event.interested_count,
    contacts: (event.contacts ?? []) as Account[],
    allowAnonymousParticipation: event.allow_anonymous_participation === true,
    longEvent: event.long_event === true || deriveLongEvent(startTime, endTime),
    startTime,
    endTime,
    timezone: event.timezone ?? null,
  }

  if (!mz) {
    return {
      ...base,
      title: event.name,
      description: event.summary,
      coverUrl: null as string | null,
      locationName: event.location?.name ?? null,
      locationAddress: event.location?.address ?? null,
      onlineUrl: event.location?.online_url ?? null,
      attendees: event.attendees_count,
      category: event.category ?? null,
      language: normalizeLanguage(event.language),
      postalCode: null as string | null,
      tags: mergeTags(event.tags, null),
      relatedEvents: [] as MobilizonRelatedEvent[],
    }
  }

  const physical = mz.physicalAddress ?? null
  const joinedAddress = physical
    ? [physical.street, physical.locality, physical.region, physical.country]
        .filter((part): part is string => !!part?.trim())
        .join(', ')
    : null

  return {
    ...base,
    title: mz.title?.trim() || event.name,
    description: mz.description?.trim() || event.summary,
    coverUrl: mz.picture?.url ?? null,
    locationName: physical?.description ?? event.location?.name ?? null,
    locationAddress: joinedAddress || event.location?.address || null,
    onlineUrl: mz.onlineAddress || event.location?.online_url || null,
    attendees: Math.max(
      event.attendees_count ?? 0,
      mz.participantStats?.participant ?? 0,
      mz.participantStats?.going ?? 0
    ),
    category: mz.category ?? event.category ?? null,
    language: normalizeLanguage(mz.language ?? event.language ?? null),
    postalCode: physical?.postalCode ?? null,
    tags: mergeTags(event.tags, mz.tags ?? null),
    relatedEvents: mz.relatedEvents ?? [],
  }
}

export default function EventDetailView({
  post,
  readOnly = false,
  hideCommentsLink = false,
}: EventDetailViewProps) {
  const theme = useTheme()
  const event = post.pf_event
  const rsvpAvailable = useEventRsvpAvailable()
  const { data: mz } = useEventEnrichment(post)
  const { handleRsvp, isRsvpPending } = useRsvpMutation({
    profileId: post.account?.id,
  })

  if (!event) return null

  const rsvp = event.my_participation?.status ?? null
  const past = isEventPast(event)
  const onSelectRsvp = (action: RsvpAction) => handleRsvp(post.id, action)
  const showRsvpPill = !readOnly && rsvpAvailable

  const merged = mergeEvent(event, mz ?? null)
  const when = formatRange(
    merged.startTime,
    merged.endTime,
    merged.isOpenEnded,
    merged.timezone
  )
  const multiDay = formatMultiDayLabel(
    merged.startTime,
    merged.endTime,
    merged.longEvent,
    merged.timezone
  )
  const deviceZone = deviceTimeZone()
  const zoneSuffix =
    merged.timezone && deviceZone && merged.timezone !== deviceZone
      ? merged.timezone
      : null
  const cancelled = event.status === 'cancelled'
  const originUrl = post.pf_source?.remote_url ?? event.external_url ?? null
  const originHost = hostnameFrom(originUrl)
  const commentsAllowed = event.allow_comments !== false
  const replyCount = getReplyCount(post)

  // Description: prefer Mobilizon HTML if available, else the Pixelfed content
  // (which may contain the ⭐ hint — strip it).
  const bodyHtml =
    merged.description ||
    preprocessStatusContent(post.content ?? '') ||
    event.summary ||
    ''

  const gotoMention = (username: string) =>
    router.push(`/profile/0?byUsername=${username.slice(1)}`)
  const gotoHashtag = (tag: string) => router.push(`/hashtag/${tag}`)
  const mentionPress = onMentionPressMethod(
    gotoMention,
    post.account?.url ?? `https://${originHost ?? ''}`,
    {
      mentions: post.mentions,
      gotoProfileById: (id: string) => router.push(`/profile/${id}`),
    }
  )

  return (
    <YStack backgroundColor={theme.background.val}>
      {merged.coverUrl ? (
        // Mobilizon's cover URL ships without intrinsic dimensions, so we
        // fall back to a 16:9 hero aspect — wider than 4:3 to favour
        // landscape framing for event banners.
        <SaveableMedia
          uri={merged.coverUrl}
          kind="image"
          shareUrl={post.url ?? undefined}
        >
          <ImageComponent
            source={{ uri: merged.coverUrl }}
            style={{ width: '100%', aspectRatio: 16 / 9 } as any}
            resizeMode={'cover' as any}
          />
        </SaveableMedia>
      ) : post.media_attachments?.[0]?.url ? (
        <SaveableMedia
          attachment={post.media_attachments[0]}
          kind="image"
          shareUrl={post.url ?? undefined}
        >
          <ImageComponent
            source={{ uri: toImageUri(post.media_attachments[0]) }}
            style={
              {
                width: '100%',
                aspectRatio: getMediaAspectRatio(post.media_attachments[0], 16 / 9),
              } as any
            }
            resizeMode={'cover' as any}
          />
        </SaveableMedia>
      ) : null}

      <YStack padding={16} gap="$3">
        {cancelled ? (
          <Text fontSize="$3" fontWeight="bold" color={theme.red10?.val ?? '#c00'}>
            CANCELLED
          </Text>
        ) : null}

        <Text fontSize="$7" fontWeight="bold" color={theme.color.val}>
          {merged.title}
        </Text>

        <XStack gap="$3" alignItems="center">
          <YStack flex={1} gap="$3">
            {when ? (
              <YStack gap="$1">
                <XStack alignItems="center" gap="$2">
                  <Feather
                    name="calendar"
                    size={ICON.sm}
                    color={theme.colorSecondary.val}
                  />
                  <Text fontSize="$4" color={theme.colorSecondary.val} flexShrink={1}>
                    {when}
                  </Text>
                </XStack>
                {zoneSuffix ? (
                  <Text fontSize="$2" color={theme.colorTertiary.val} marginLeft={24}>
                    {zoneSuffix}
                  </Text>
                ) : null}
                {multiDay ? (
                  <Text fontSize="$2" color={theme.colorTertiary.val} marginLeft={24}>
                    {multiDay}
                  </Text>
                ) : null}
              </YStack>
            ) : null}

            {merged.locationName || merged.locationAddress ? (
              <XStack alignItems="flex-start" gap="$2">
                <Feather name="map-pin" size={ICON.sm} color={theme.colorSecondary.val} />
                <YStack flexShrink={1}>
                  {merged.locationName ? (
                    <Text fontSize="$4" color={theme.color.val}>
                      {merged.locationName}
                    </Text>
                  ) : null}
                  {merged.locationAddress ? (
                    <Text fontSize="$3" color={theme.colorSecondary.val}>
                      {merged.locationAddress}
                    </Text>
                  ) : null}
                  {merged.postalCode ? (
                    <Text fontSize="$2" color={theme.colorTertiary.val}>
                      {merged.postalCode}
                    </Text>
                  ) : null}
                </YStack>
              </XStack>
            ) : null}

            {merged.onlineUrl ? (
              <Pressable onPress={() => openBrowserAsync(merged.onlineUrl as string)}>
                <XStack alignItems="center" gap="$2">
                  <Feather
                    name="video"
                    size={ICON.sm}
                    color={theme.interactiveActive.val}
                  />
                  <Text
                    fontSize="$3"
                    color={theme.interactiveActive.val}
                    numberOfLines={1}
                    flexShrink={1}
                  >
                    {merged.onlineUrl}
                  </Text>
                </XStack>
              </Pressable>
            ) : null}

            {!event.hide_attendee_count && merged.attendees != null ? (
              <XStack alignItems="center" gap="$2">
                <Feather name="users" size={ICON.sm} color={theme.colorTertiary.val} />
                <Text fontSize="$3" color={theme.colorTertiary.val}>
                  {prettyCount(merged.attendees)} going
                  {merged.interested
                    ? ` · ${prettyCount(merged.interested)} interested`
                    : ''}
                </Text>
              </XStack>
            ) : null}
          </YStack>

          {showRsvpPill ? (
            <View justifyContent="center" maxWidth="40%">
              <RsvpPill
                rsvp={rsvp}
                past={past}
                pending={isRsvpPending}
                onSelect={onSelectRsvp}
                size="md"
              />
            </View>
          ) : null}
        </XStack>

        {readOnly ? (
          <View
            padding={10}
            borderRadius={8}
            backgroundColor={theme.backgroundTertiary.val}
          >
            <Text fontSize="$2" color={theme.colorSecondary.val}>
              Your server hasn't federated with this event's origin yet. Open it on
              {originHost ? ` ${originHost}` : ' the origin instance'} to interact.
            </Text>
          </View>
        ) : null}

        {bodyHtml ? (
          <YStack gap="$1">
            <Separator borderColor={theme.borderColor.val} />
            <View marginTop="$2">
              <StatusContent
                html={bodyHtml}
                onMentionPress={mentionPress}
                onHashtagPress={gotoHashtag}
              />
            </View>
          </YStack>
        ) : null}

        {merged.tags.length > 0 ? (
          <YStack gap="$2" marginTop="$2">
            <Separator borderColor={theme.borderColor.val} />
            <Text fontSize="$3" fontWeight="bold" color={theme.colorSecondary.val}>
              Tags
            </Text>
            <XStack flexWrap="wrap" gap="$2">
              {merged.tags.map((tag, i) => (
                <Link
                  key={tag.slug || tag.title || `tag-${i}`}
                  href={`/hashtag/${tag.slug || tag.title}`}
                  asChild
                >
                  <Pressable hitSlop={4}>
                    <View
                      paddingHorizontal={8}
                      paddingVertical={4}
                      borderRadius={6}
                      backgroundColor={theme.backgroundTertiary.val}
                    >
                      <Text fontSize="$2" color={theme.color.val}>
                        #{tag.title}
                      </Text>
                    </View>
                  </Pressable>
                </Link>
              ))}
            </XStack>
          </YStack>
        ) : null}

        {merged.category || merged.language ? (
          <XStack gap="$4" marginTop="$2">
            {merged.category ? (
              <Text fontSize="$2" color={theme.colorTertiary.val}>
                Category: {merged.category}
              </Text>
            ) : null}
            {merged.language ? (
              <Text fontSize="$2" color={theme.colorTertiary.val}>
                Language: {merged.language}
              </Text>
            ) : null}
          </XStack>
        ) : null}

        {merged.relatedEvents.length > 0 ? (
          <YStack gap="$2" marginTop="$2">
            <Separator borderColor={theme.borderColor.val} />
            <Text fontSize="$3" fontWeight="bold" color={theme.colorSecondary.val}>
              Related events
            </Text>
            {merged.relatedEvents.map((rel, i) => (
              <View
                key={rel.uuid || `rel-${i}`}
                paddingVertical={6}
                borderBottomWidth={1}
                borderBottomColor={theme.borderColor.val}
              >
                <Text fontSize="$3" color={theme.color.val} numberOfLines={1}>
                  {rel.title}
                </Text>
                {rel.beginsOn ? (
                  <Text fontSize="$2" color={theme.colorTertiary.val}>
                    {new Date(rel.beginsOn).toLocaleString()}
                  </Text>
                ) : null}
              </View>
            ))}
          </YStack>
        ) : null}

        {event.organizers?.length ? (
          <YStack gap="$2" marginTop="$2">
            <Separator borderColor={theme.borderColor.val} />
            <Text fontSize="$3" fontWeight="bold" color={theme.colorSecondary.val}>
              {event.organizers.length > 1 ? 'Organizers' : 'Organizer'}
            </Text>
            {event.organizers.map((org, i) => {
              if (isHydratedAccount(org)) {
                return (
                  <View key={org.id}>
                    <Link href={`/profile/${org.id}`} asChild>
                      <Pressable hitSlop={4}>
                        <XStack alignItems="center" gap="$2">
                          <UserAvatar url={org.avatar} size="sm" />
                          <Text fontSize="$3" color={theme.color.val}>
                            {org.display_name || org.acct}
                          </Text>
                        </XStack>
                      </Pressable>
                    </Link>
                  </View>
                )
              }

              if (!org.actor_uri) return null
              const handle = rawOrganizerHandle(org)
              return (
                <View key={`${org.actor_uri}-${i}`}>
                  <Pressable
                    hitSlop={4}
                    onPress={() => openBrowserAsync(org.actor_uri)}
                    accessibilityRole="link"
                    accessibilityLabel={`Open ${handle} on origin`}
                  >
                    <XStack alignItems="center" gap="$2">
                      <UserAvatar url={null} size="sm" />
                      <YStack flexShrink={1}>
                        <Text fontSize="$3" color={theme.color.val} numberOfLines={1}>
                          {handle}
                        </Text>
                      </YStack>
                      <Feather
                        name="external-link"
                        size={ICON.xs}
                        color={theme.colorTertiary.val}
                      />
                    </XStack>
                  </Pressable>
                </View>
              )
            })}
          </YStack>
        ) : null}

        {merged.contacts.length > 0 ? (
          <YStack gap="$2" marginTop="$2">
            <Separator borderColor={theme.borderColor.val} />
            <Text fontSize="$3" fontWeight="bold" color={theme.colorSecondary.val}>
              Contact
            </Text>
            {merged.contacts.map((contact) => (
              <View key={contact.id}>
                <Link href={`/profile/${contact.id}`} asChild>
                  <Pressable>
                    <XStack alignItems="center" gap="$2">
                      <UserAvatar url={contact.avatar} size="sm" />
                      <Text fontSize="$3" color={theme.color.val}>
                        {contact.display_name || contact.acct}
                      </Text>
                    </XStack>
                  </Pressable>
                </Link>
              </View>
            ))}
          </YStack>
        ) : null}

        <YStack gap="$2" marginTop="$2">
          <Separator borderColor={theme.borderColor.val} />
          {commentsAllowed && !hideCommentsLink ? (
            <Pressable onPress={() => router.push(`/post/${post.id}`)}>
              <View
                padding={12}
                borderRadius={8}
                backgroundColor={theme.backgroundTertiary.val}
              >
                <XStack alignItems="center" gap="$2">
                  <Feather
                    name="message-circle"
                    size={ICON.sm}
                    color={theme.interactiveActive.val}
                  />
                  <Text fontSize="$3" color={theme.interactiveActive.val}>
                    {replyCount > 0
                      ? `View comments · ${prettyCount(replyCount)}`
                      : 'Be the first to comment'}
                  </Text>
                </XStack>
              </View>
            </Pressable>
          ) : (
            <Text fontSize="$2" color={theme.colorTertiary.val}>
              Comments disabled by organizer
            </Text>
          )}

          {originUrl ? (
            <Pressable onPress={() => safeOpenOriginUrl(originUrl, post.uri)}>
              <View
                padding={12}
                borderRadius={8}
                backgroundColor={theme.backgroundTertiary.val}
              >
                <XStack alignItems="center" gap="$2">
                  <Feather
                    name="external-link"
                    size={ICON.sm}
                    color={theme.interactiveActive.val}
                  />
                  <Text fontSize="$3" color={theme.interactiveActive.val}>
                    Open on {originHost ?? 'origin'}
                  </Text>
                </XStack>
              </View>
            </Pressable>
          ) : null}
        </YStack>
      </YStack>
    </YStack>
  )
}
