import {
  NDKEvent,
  NDKFilter,
  NDKKind,
  NDKSubscriptionCacheUsage,
  NDKUserProfile,
  profileFromEvent
} from '@nostr-dev-kit/ndk'
import { ErrorBoundary } from 'components/ErrorBoundary'
import { GameCard } from 'components/GameCard'
import { LoadingSpinner } from 'components/LoadingSpinner'
import { ModCard } from 'components/ModCard'
import { ModFilter } from 'components/ModsFilter'
import { Pagination } from 'components/Pagination'
import { Profile } from 'components/ProfileSection'
import {
  MAX_GAMES_PER_PAGE,
  MAX_MODS_PER_PAGE,
  T_TAG_VALUE
} from 'constants.ts'
import {
  useAppSelector,
  useFilteredMods,
  useGames,
  useMuteLists,
  useNDKContext,
  useNSFWList
} from 'hooks'
import React, {
  Dispatch,
  SetStateAction,
  useEffect,
  useMemo,
  useRef,
  useState
} from 'react'
import { useSearchParams } from 'react-router-dom'
import {
  FilterOptions,
  ModDetails,
  ModeratedFilter,
  MuteLists,
  NSFWFilter,
  SortBy
} from 'types'
import {
  extractModData,
  isModDataComplete,
  log,
  LogType,
  scrollIntoView
} from 'utils'

enum SearchKindEnum {
  Mods = 'Mods',
  Games = 'Games',
  Users = 'Users'
}

export const SearchPage = () => {
  const scrollTargetRef = useRef<HTMLDivElement>(null)
  const [searchParams] = useSearchParams()

  const muteLists = useMuteLists()
  const nsfwList = useNSFWList()
  const searchTermRef = useRef<HTMLInputElement>(null)

  const [searchKind, setSearchKind] = useState(
    (searchParams.get('searching') as SearchKindEnum) || SearchKindEnum.Mods
  )

  const [filterOptions, setFilterOptions] = useState<FilterOptions>({
    sort: SortBy.Latest,
    nsfw: NSFWFilter.Hide_NSFW,
    source: window.location.host,
    moderated: ModeratedFilter.Moderated
  })

  const [searchTerm, setSearchTerm] = useState(
    searchParams.get('searchTerm') || ''
  )

  const handleSearch = () => {
    const value = searchTermRef.current?.value || '' // Access the input value from the ref
    setSearchTerm(value)
  }

  // Handle "Enter" key press inside the input
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (event.key === 'Enter') {
      handleSearch()
    }
  }

  return (
    <div className='InnerBodyMain'>
      <div className='ContainerMain'>
        <div
          className='IBMSecMainGroup IBMSecMainGroupAlt'
          ref={scrollTargetRef}
        >
          <div className='IBMSecMain'>
            <div className='SearchMainWrapper'>
              <div className='IBMSMTitleMain'>
                <h2 className='IBMSMTitleMainHeading'>
                  Search:&nbsp;
                  <span className='IBMSMTitleMainHeadingSpan'>
                    {searchTerm}
                  </span>
                </h2>
              </div>
              <div className='SearchMain'>
                <div className='SearchMainInside'>
                  <div className='SearchMainInsideWrapper'>
                    <input
                      type='text'
                      className='SMIWInput'
                      ref={searchTermRef}
                      onKeyDown={handleKeyDown}
                      placeholder='Enter search term'
                    />
                    <button
                      className='btn btnMain SMIWButton'
                      type='button'
                      onClick={handleSearch}
                    >
                      <svg
                        xmlns='http://www.w3.org/2000/svg'
                        viewBox='0 0 512 512'
                        width='1em'
                        height='1em'
                        fill='currentColor'
                      >
                        <path d='M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z'></path>
                      </svg>
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <Filters
            filterOptions={filterOptions}
            setFilterOptions={setFilterOptions}
            searchKind={searchKind}
            setSearchKind={setSearchKind}
          />
          {searchKind === SearchKindEnum.Mods && (
            <ModsResult
              searchTerm={searchTerm}
              filterOptions={filterOptions}
              muteLists={muteLists}
              nsfwList={nsfwList}
              el={scrollTargetRef.current}
            />
          )}
          {searchKind === SearchKindEnum.Users && (
            <UsersResult
              searchTerm={searchTerm}
              muteLists={muteLists}
              moderationFilter={filterOptions.moderated}
            />
          )}
          {searchKind === SearchKindEnum.Games && (
            <GamesResult searchTerm={searchTerm} />
          )}
        </div>
      </div>
    </div>
  )
}

type FiltersProps = {
  filterOptions: FilterOptions
  setFilterOptions: Dispatch<SetStateAction<FilterOptions>>
  searchKind: SearchKindEnum
  setSearchKind: Dispatch<SetStateAction<SearchKindEnum>>
}

const Filters = React.memo(
  ({
    filterOptions,
    setFilterOptions,
    searchKind,
    setSearchKind
  }: FiltersProps) => {
    const userState = useAppSelector((state) => state.user)

    return (
      <div className='IBMSecMain'>
        <div className='FiltersMain'>
          {searchKind === SearchKindEnum.Mods && (
            <ModFilter
              filterOptions={filterOptions}
              setFilterOptions={setFilterOptions}
            />
          )}

          {searchKind === SearchKindEnum.Users && (
            <div className='FiltersMainElement'>
              <div className='dropdown dropdownMain'>
                <button
                  className='btn dropdown-toggle btnMain btnMainDropdown'
                  aria-expanded='false'
                  data-bs-toggle='dropdown'
                  type='button'
                >
                  {filterOptions.moderated}
                </button>
                <div className='dropdown-menu dropdownMainMenu'>
                  {Object.values(ModeratedFilter).map((item, index) => {
                    if (item === ModeratedFilter.Unmoderated_Fully) {
                      const isAdmin =
                        userState.user?.npub ===
                        import.meta.env.VITE_REPORTING_NPUB

                      if (!isAdmin) return null
                    }

                    return (
                      <div
                        key={`moderatedFilterItem-${index}`}
                        className='dropdown-item dropdownMainMenuItem'
                        onClick={() =>
                          setFilterOptions((prev) => ({
                            ...prev,
                            moderated: item
                          }))
                        }
                      >
                        {item}
                      </div>
                    )
                  })}
                </div>
              </div>
            </div>
          )}

          <div className='FiltersMainElement'>
            <div className='dropdown dropdownMain'>
              <button
                className='btn dropdown-toggle btnMain btnMainDropdown'
                aria-expanded='false'
                data-bs-toggle='dropdown'
                type='button'
              >
                Searching: {searchKind}
              </button>
              <div className='dropdown-menu dropdownMainMenu'>
                {Object.values(SearchKindEnum).map((item, index) => (
                  <div
                    key={`searchingFilterItem-${index}`}
                    className='dropdown-item dropdownMainMenuItem'
                    onClick={() => setSearchKind(item)}
                  >
                    {item}
                  </div>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
)

type ModsResultProps = {
  filterOptions: FilterOptions
  searchTerm: string
  muteLists: {
    admin: MuteLists
    user: MuteLists
  }
  nsfwList: string[]
  el: HTMLElement | null
}

const ModsResult = ({
  filterOptions,
  searchTerm,
  muteLists,
  nsfwList,
  el
}: ModsResultProps) => {
  const { ndk } = useNDKContext()
  const [mods, setMods] = useState<ModDetails[]>([])
  const [page, setPage] = useState(1)
  const userState = useAppSelector((state) => state.user)

  useEffect(() => {
    const filter: NDKFilter = {
      kinds: [NDKKind.Classified],
      '#t': [T_TAG_VALUE]
    }

    const subscription = ndk.subscribe(filter, {
      cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
      closeOnEose: true
    })

    subscription.on('event', (ndkEvent) => {
      if (isModDataComplete(ndkEvent)) {
        const mod = extractModData(ndkEvent)
        setMods((prev) => {
          if (prev.find((e) => e.aTag === mod.aTag)) return [...prev]

          return [...prev, mod]
        })
      }
    })

    // Cleanup function to stop all subscriptions
    return () => {
      subscription.stop()
    }
  }, [ndk])

  useEffect(() => {
    scrollIntoView(el)
    setPage(1)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [searchTerm])

  const filteredMods = useMemo(() => {
    if (searchTerm === '') return []

    const lowerCaseSearchTerm = searchTerm.toLowerCase()

    const filterFn = (mod: ModDetails) =>
      mod.title.toLowerCase().includes(lowerCaseSearchTerm) ||
      mod.game.toLowerCase().includes(lowerCaseSearchTerm) ||
      mod.summary.toLowerCase().includes(lowerCaseSearchTerm) ||
      mod.body.toLowerCase().includes(lowerCaseSearchTerm) ||
      mod.tags.findIndex((tag) =>
        tag.toLowerCase().includes(lowerCaseSearchTerm)
      ) > -1

    return mods.filter(filterFn)
  }, [mods, searchTerm])

  const filteredModList = useFilteredMods(
    filteredMods,
    userState,
    filterOptions,
    nsfwList,
    muteLists
  )

  const handleNext = () => {
    scrollIntoView(el)
    setPage((prev) => prev + 1)
  }

  const handlePrev = () => {
    scrollIntoView(el)
    setPage((prev) => prev - 1)
  }

  return (
    <>
      <div className='IBMSecMain IBMSMListWrapper'>
        <div className='IBMSMList'>
          {filteredModList
            .slice((page - 1) * MAX_MODS_PER_PAGE, page * MAX_MODS_PER_PAGE)
            .map((mod) => (
              <ModCard key={mod.id} {...mod} />
            ))}
        </div>
      </div>
      <Pagination
        page={page}
        disabledNext={filteredModList.length <= page * MAX_MODS_PER_PAGE}
        handlePrev={handlePrev}
        handleNext={handleNext}
      />
    </>
  )
}

type UsersResultProps = {
  searchTerm: string
  moderationFilter: ModeratedFilter
  muteLists: {
    admin: MuteLists
    user: MuteLists
  }
}

const UsersResult = ({
  searchTerm,
  moderationFilter,
  muteLists
}: UsersResultProps) => {
  const { fetchEvents } = useNDKContext()
  const [isFetching, setIsFetching] = useState(false)
  const [profiles, setProfiles] = useState<NDKUserProfile[]>([])

  const userState = useAppSelector((state) => state.user)

  useEffect(() => {
    if (searchTerm === '') {
      setProfiles([])
    } else {
      const filter: NDKFilter = {
        kinds: [NDKKind.Metadata],
        search: searchTerm
      }

      setIsFetching(true)
      fetchEvents(filter)
        .then((events) => {
          const results = events.map((event) => {
            const ndkEvent = new NDKEvent(undefined, event)
            const profile = profileFromEvent(ndkEvent)
            return profile
          })
          setProfiles(results)
        })
        .catch((err) => {
          log(true, LogType.Error, 'An error occurred in fetching users', err)
        })
        .finally(() => {
          setIsFetching(false)
        })
    }
  }, [searchTerm, fetchEvents])

  const filteredProfiles = useMemo(() => {
    let filtered = [...profiles]
    const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
    const isUnmoderatedFully =
      moderationFilter === ModeratedFilter.Unmoderated_Fully

    // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully"
    if (!(isAdmin && isUnmoderatedFully)) {
      filtered = filtered.filter(
        (profile) => !muteLists.admin.authors.includes(profile.pubkey as string)
      )
    }

    if (moderationFilter === ModeratedFilter.Moderated) {
      filtered = filtered.filter(
        (profile) => !muteLists.user.authors.includes(profile.pubkey as string)
      )
    }

    return filtered
  }, [userState.user?.npub, moderationFilter, profiles, muteLists])
  return (
    <>
      {isFetching && <LoadingSpinner desc='Fetching Profiles' />}
      <div className='IBMSecMain IBMSMListWrapper'>
        <div className='IBMSMList'>
          {filteredProfiles.map((profile) => {
            if (profile.pubkey) {
              const displayName =
                profile?.displayName || profile?.name || '[name not set up]'
              const about = profile?.bio || profile?.about || '[bio not set up]'

              return (
                <ErrorBoundary key={profile.pubkey}>
                  <Profile
                    pubkey={profile.pubkey as string}
                    displayName={displayName}
                    about={about}
                    image={profile?.image}
                    nip05={profile?.nip05}
                    lud16={profile?.lud16}
                  />
                </ErrorBoundary>
              )
            }

            return null
          })}
        </div>
      </div>
    </>
  )
}

type GamesResultProps = {
  searchTerm: string
}

const GamesResult = ({ searchTerm }: GamesResultProps) => {
  const games = useGames()
  const [page, setPage] = useState(1)

  // Reset the page to 1 whenever searchTerm changes
  useEffect(() => {
    setPage(1)
  }, [searchTerm])

  const filteredGames = useMemo(() => {
    if (searchTerm === '') return []

    const lowerCaseSearchTerm = searchTerm.toLowerCase()

    return games.filter((game) =>
      game['Game Name'].toLowerCase().includes(lowerCaseSearchTerm)
    )
  }, [searchTerm, games])

  const handleNext = () => {
    setPage((prev) => prev + 1)
  }

  const handlePrev = () => {
    setPage((prev) => prev - 1)
  }

  return (
    <>
      <div className='IBMSecMain IBMSMListWrapper'>
        <div className='IBMSMList IBMSMListFeaturedAlt'>
          {filteredGames
            .slice((page - 1) * MAX_GAMES_PER_PAGE, page * MAX_GAMES_PER_PAGE)
            .map((game) => (
              <GameCard
                key={game['Game Name']}
                title={game['Game Name']}
                imageUrl={game['Boxart image']}
              />
            ))}
        </div>
      </div>
      <Pagination
        page={page}
        disabledNext={filteredGames.length <= page * MAX_GAMES_PER_PAGE}
        handlePrev={handlePrev}
        handleNext={handleNext}
      />
    </>
  )
}
