import type { DefaultDocumentIDType, Where } from 'payload'

import configPromise from '@payload-config'
import { Star, UserRound, BadgeCheck, ExternalLink } from 'lucide-react'
import { draftMode } from 'next/headers'
import Link from 'next/link'
import { getPayload } from 'payload'
import React from 'react'

import { Media } from '@/components/Media'
import type {
  BrandReview,
  BrandReviewsBlock as BrandReviewsBlockProps,
  Media as MediaType,
} from '@/payload-types'
import { cn } from '@/utilities/cn'

type RelationshipValue = { id?: number | string } | number | string | null | undefined

const sourceLabelMap = {
  facebook: 'Facebook',
  googleBusiness: 'Google',
  manual: 'Bunga Mekarsari',
} as const

const getRelationshipID = (value: RelationshipValue): number | string | null => {
  if (typeof value === 'string' || typeof value === 'number') {
    return value
  }

  if (value && typeof value === 'object') {
    return typeof value.id === 'string' || typeof value.id === 'number' ? value.id : null
  }

  return null
}

const isBrandReview = (value: unknown): value is BrandReview =>
  value !== null &&
  typeof value === 'object' &&
  'reviewerName' in value &&
  'reviewText' in value &&
  'rating' in value

const isMediaResource = (value: unknown): value is MediaType =>
  value !== null && typeof value === 'object' && 'url' in value

const resolveLimit = (limit?: number | null): number => {
  if (typeof limit !== 'number' || !Number.isFinite(limit)) {
    return 4
  }

  return Math.min(Math.max(Math.floor(limit), 1), 12)
}

const normalizeRating = (rating?: number | null): number => {
  if (typeof rating !== 'number' || !Number.isFinite(rating)) {
    return 5
  }

  return Math.min(Math.max(rating, 1), 5)
}

const StarRating: React.FC<{ rating?: number | null }> = ({ rating }) => {
  const normalizedRating = normalizeRating(rating)
  const filledStars = Math.round(normalizedRating)

  return (
    <div
      aria-label={`${normalizedRating.toFixed(1)} dari 5 bintang`}
      className="mb-4 flex text-[#FFB800]"
    >
      {Array.from({ length: 5 }, (_, index) => {
        const isFilled = index < filledStars

        return (
          <Star
            className={cn('h-5 w-5', isFilled ? 'fill-current' : 'fill-transparent opacity-40')}
            key={index}
          />
        )
      })}
    </div>
  )
}

export const BrandReviewsBlock: React.FC<
  BrandReviewsBlockProps & {
    id?: DefaultDocumentIDType
    className?: string
  }
> = async (props) => {
  const {
    className,
    description,
    heading,
    id,
    limit: limitFromProps,
    populateBy,
    selectedDocs,
    sources,
  } = props
  const { isEnabled: draft } = await draftMode()
  const payload = await getPayload({ config: configPromise })
  const limit = resolveLimit(limitFromProps)

  let reviews: BrandReview[] = []

  if (populateBy === 'selection' && selectedDocs?.length) {
    const selectedIDs = selectedDocs
      .map((review) => getRelationshipID(review))
      .filter((value): value is number | string => value !== null)
      .slice(0, limit)

    if (selectedIDs.length > 0) {
      const fetchedReviews = await payload.find({
        collection: 'brandReviews',
        depth: 1,
        limit: selectedIDs.length,
        overrideAccess: draft,
        pagination: false,
        where: {
          id: {
            in: selectedIDs,
          },
        },
      })
      const reviewsByID = new Map(fetchedReviews.docs.map((review) => [String(review.id), review]))

      reviews = selectedIDs
        .map((reviewID) => reviewsByID.get(String(reviewID)))
        .filter((review): review is BrandReview => Boolean(review))
    } else {
      reviews = selectedDocs.filter(isBrandReview).slice(0, limit)
    }
  } else {
    const sourceWhere: Where | undefined =
      sources && sources.length > 0
        ? {
            source: {
              in: sources,
            },
          }
        : undefined

    const where: Where = {
      and: [
        {
          status: {
            equals: 'published',
          },
        },
        ...(sourceWhere ? [sourceWhere] : []),
      ],
    }

    const fetchedReviews = await payload.find({
      collection: 'brandReviews',
      depth: 1,
      limit,
      overrideAccess: draft,
      pagination: false,
      sort: 'sortOrder',
      where,
    })

    reviews = fetchedReviews.docs
  }

  if (!reviews.length) {
    return null
  }

  return (
    <section className={cn('py-16', className)} id={id ? `block-${id}` : undefined}>
      <div className="mx-auto max-w-7xl px-4 md:px-16">
        <div className="mb-8 text-center md:mb-12">
          {heading ? (
            <h2 className="mb-4 font-serif text-3xl font-bold leading-tight text-primary md:text-4xl">
              {heading}
            </h2>
          ) : null}
          {description ? (
            <p className="mx-auto max-w-2xl text-base leading-relaxed text-muted-foreground md:text-lg">
              {description}
            </p>
          ) : null}
        </div>

        <div className="grid grid-cols-1 gap-8 md:grid-cols-2">
          {reviews.map((review, index) => {
            const avatar = isMediaResource(review.avatar) ? review.avatar : null
            const sourceLabel =
              review.source && review.source in sourceLabelMap
                ? sourceLabelMap[review.source as keyof typeof sourceLabelMap]
                : 'Review'

            return (
              <article
                className="flex h-full flex-col rounded-2xl border border-border/40 bg-white p-8 shadow-sm transition-shadow hover:shadow-md dark:bg-card"
                key={review.id ?? `${review.reviewerName}-${index}`}
              >
                <StarRating rating={review.rating} />

                <p className="mb-6 flex-grow text-base italic leading-relaxed text-muted-foreground">
                  &quot;{review.reviewText}&quot;
                </p>

                <div className="flex items-center justify-between gap-4">
                  <div className="flex min-w-0 items-center gap-3">
                    <div className="relative flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary/10 text-primary">
                      {avatar ? (
                        <Media
                          alt={review.reviewerName}
                          fill
                          htmlElement={null}
                          imgClassName="object-cover"
                          resource={avatar}
                        />
                      ) : (
                        <UserRound className="h-5 w-5" />
                      )}
                    </div>
                    <div className="min-w-0">
                      <p className="truncate text-sm font-bold text-primary">
                        {review.reviewerName}
                      </p>
                      {review.reviewerLocation ? (
                        <p className="truncate text-xs text-muted-foreground/70">
                          {review.reviewerLocation}
                        </p>
                      ) : null}
                    </div>
                  </div>

                  <div className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
                    {review.isVerified ? <BadgeCheck className="h-4 w-4 text-secondary" /> : null}
                    {review.sourceUrl ? (
                      <Link
                        aria-label={`Buka review ${review.reviewerName} di ${sourceLabel}`}
                        className="inline-flex items-center gap-1 hover:text-primary"
                        href={review.sourceUrl}
                        rel="noopener noreferrer"
                        target="_blank"
                      >
                        {sourceLabel}
                        <ExternalLink className="h-3.5 w-3.5" />
                      </Link>
                    ) : (
                      <span>{sourceLabel}</span>
                    )}
                  </div>
                </div>
              </article>
            )
          })}
        </div>
      </div>
    </section>
  )
}
