import { Alert } from '@mui/material'
import axios from 'axios'
import _find from 'lodash/find'
import _forEach from 'lodash/forEach'
import _get from 'lodash/get'
import _identity from 'lodash/identity'
import _isEmpty from 'lodash/isEmpty'
import _isEqual from 'lodash/isEqual'
import _keys from 'lodash/keys'
import _map from 'lodash/map'
import _values from 'lodash/values'
import moment from 'moment'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import Countdown, { CountdownRenderProps } from 'react-countdown'

import {
  getSeatMapData,
  getSeatMapStatuses,
  removeSeatReserve,
  reserveSeat,
} from '../../api'
import { useOnline } from '../../hooks/useOnline'
import { showZero } from '../../utils/showZero'
import { ReferralLogic } from '../ticketsContainer/ReferralLogic'
import { addToCartFunc } from './addToCart'
import { SeatMapComponent } from './SeatMapComponent'
import { TicketsSection } from './TicketsSection'
import {
  getAddToCartRequestData,
  getOwnReservationsBasedOnStatuses,
  getTicketDropdownData,
  getTierIdBasedOnSeatId,
} from './utils'

const OFFLINE_MESSAGE = 'Offline.'

interface IGuestCounts {
  [key: string]: number;
}

export const SeatMapContainer = (props: IMapContainerProps) => {
  const {
    event: {
      id: eventId,
      currency: { symbol },
      tableMapEnabled,
      country,
    },
    mapContainerId,
    timerMessage = '',
    onAddToCartSuccess = _identity,
    onCountdownFinish = _identity,
    ticketDeleteButtonContent,
    ticketInfoContent,
  } = props

  const [seatMapData, setSeatMapData] = useState({
    seatMap: '',
  } as {
    seatMap: string;
    seatReservationTime: number;
    seatMapType: string | null;
    tierPrices: any;
    predefinedSeats: any;
  })
  const eventSeatsRef = useRef([] as EventSeat[])
  const ticketTypeTierRelationsRef = useRef({} as TicketTypeTierRelations)
  const [selectedTickets, setSelectedTickets] = useState<{
    [seatId: string]: string;
  }>({})
  const [seatMapStatuses, setSeatMapStatuses] = useState('')
  const [reservedSeats, setReservedSeats] = useState<Array<SeatReservationData>>([])
  const [isLoadingSeatMapData, setIsLoadingSeatMapData] = useState(true)
  const [isLoadingStatuses, setIsLoadingStatuses] = useState(false)
  const [isReserving, setIsReserving] = useState(false)
  const [isAddingToCart, setIsAddingToCart] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [showTimer, setShowTimer] = useState(
    Date.now() <= Number(localStorage.getItem(`reservationStart-${eventId}`))
  )
  const [guestCounts, setGuestCounts] = useState<IGuestCounts>({} as IGuestCounts)
  const [isOnline, setIsOnline] = useState(true)
  const isGuestCountsSet = useRef(false)

  useOnline(setIsOnline)

  const updateGuestCounts = (data: any) => {
    _forEach(data, (item: any) => {
      const tierTickets = ticketTypeTierRelationsRef.current[item.tierId]
      const seatTicketsArray = _values(tierTickets)

      setGuestCounts((prevState: any) => ({
        [item.seatId]: Number(seatTicketsArray[0].ticket_type_min_number_of_guests),
        ...prevState,
      }))
    })
  }

  const fetchSeatMapData = useCallback(async () => {
    try {
      const seatMapDataResponse = await getSeatMapData(eventId)
      const {
        data: {
          attributes: {
            seatData,
            ticketTypeTierRelations,
            eventSeats,
            seatReservationTime,
            seatMapType,
            tierPrices,
            predefinedSeats,
          },
        },
      } = seatMapDataResponse
      eventSeatsRef.current = eventSeats
      ticketTypeTierRelationsRef.current = ticketTypeTierRelations
      setSeatMapData({
        seatMap: JSON.parse(seatData),
        seatReservationTime,
        seatMapType,
        tierPrices,
        predefinedSeats,
      })
    } catch (error) {
      setError('Something went wrong')
    } finally {
      setIsLoadingSeatMapData(false)
    }
  }, [eventId])

  const fetchSeatMapReservations = useCallback(async () => {
    if (isOnline) {
      try {
        const statusesResponse = await getSeatMapStatuses(eventId)
        const statuses = _get(statusesResponse, 'data.attributes') || {}
        const reservationData: Array<SeatReservationData> = []
        const ownReservations: string[] = getOwnReservationsBasedOnStatuses(statuses)

        _forEach(ownReservations, reservation => {
          const tierIdOfReservation = getTierIdBasedOnSeatId(
            reservation,
            eventSeatsRef.current
          ) as string
          reservationData.push({
            seatId: reservation,
            tierId: tierIdOfReservation,
            type: 'reserve',
          })
        })

        localStorage.setItem('reservationData', JSON.stringify(reservationData))

        if (!_isEqual(seatMapStatuses, statusesResponse.data.attributes)) {
          setSeatMapStatuses(statusesResponse.data.attributes)
        }
        if (!_isEqual(reservationData, reservedSeats)) {
          setReservedSeats(reservationData)
        }

        // automatically set ticket/table type if it's the only one
        if (
          ticketTypeTierRelationsRef.current &&
          !_isEqual(reservationData, reservedSeats)
        ) {
          if (!isGuestCountsSet.current) {
            updateGuestCounts(reservationData)
            isGuestCountsSet.current = true
          }
          _forEach(reservationData, (item: any) => {
            const tierTickets = ticketTypeTierRelationsRef.current[item.tierId]
            const seatTicketsArray = _values(tierTickets)

            setSelectedTickets((prevState: any) => ({
              ...prevState,
              [item.seatId]:
                seatTicketsArray.length === 1 ? seatTicketsArray[0].ticket_type_id : '',
            }))
          })

          if (_isEmpty(reservationData)) {
            setGuestCounts({})
            setSelectedTickets({})
          }
        }
      } catch (error) {
        setError('Something went wrong')
      }
    }
  }, [eventId, seatMapStatuses, reservedSeats, isOnline])

  const startTimer = useCallback(
    (duration: number) => {
      setShowTimer(true)

      if (!localStorage.getItem(`reservationStart-${eventId}`)) {
        localStorage.setItem(`reservationStart-${eventId}`, String(Date.now() + duration))
      }
    },
    [eventId]
  )

  const endTimer = useCallback(() => {
    localStorage.removeItem(`reservationStart-${eventId}`)
    setShowTimer(false)
  }, [eventId])

  const handleSeatReservation = async (
    eventId: string,
    tierId: string,
    seatId: string
  ) => {
    setIsReserving(true)
    try {
      await reserveSeat(eventId, tierId, seatId)
      await fetchSeatMapReservations()

      startTimer(seatMapData.seatReservationTime * 60000)
      const reservationData = JSON.parse(localStorage.getItem('reservationData') || '')
      setReservedSeats(reservationData)
      updateGuestCounts(reservationData)

      // automatically set ticket/table type if it's the only one
      const relations = _keys(ticketTypeTierRelationsRef.current[tierId])
      const [firstItem] = _keys(ticketTypeTierRelationsRef.current[tierId])
      handleTicketSelect(relations.length === 1 ? firstItem : '', seatId)
    } catch (error) {
      setError(
        error?.response?.data?.message === 'Selected seat is not available'
          ? // eslint-disable-next-line max-len
            'No more of this ticket type are available right now - they’re either sold out or in people’s shopping carts. Try refreshing the page!'
          : 'Something went wrong'
      )
    } finally {
      setIsReserving(false)
    }
  }

  const handleCancelSeatReservtion = async (
    eventId: string,
    tierId: string,
    seatId: string
  ) => {
    setIsReserving(true)
    try {
      await removeSeatReserve(eventId, tierId, [seatId])
      await fetchSeatMapReservations()
      if (_isEmpty(JSON.parse(localStorage.getItem('reservationData') as string))) {
        endTimer()
      }
      const reservationData = JSON.parse(localStorage.getItem('reservationData') || '')
      const currentSelectedTickets = { ...selectedTickets }
      delete currentSelectedTickets[seatId]
      setReservedSeats(reservationData)
      setSelectedTickets(currentSelectedTickets)
    } catch (error) {
      setError('Something went wrong')
    } finally {
      setIsReserving(false)
    }
  }

  const onSeatClick = async (seatInfo: {
    seat: {
      tierId: string;
      seatId: string;
      status: string;
    };
  }) => {
    const {
      seat: { tierId, seatId, status },
    } = seatInfo

    const reservationData = JSON.parse(localStorage.getItem('reservationData') || '[]')
    if (status === 'B' || status === 'R' || status === 'BLOCKED' || status === 'RE')
      return
    if (_find(reservationData, data => data.seatId === seatId)) {
      handleCancelSeatReservtion(eventId, tierId, seatId)
    } else if (reservationData.length >= 10) {
      setError('Limit exceeded')
    } else {
      handleSeatReservation(eventId, tierId, seatId)
    }
  }

  const handleTicketSelect = (ticketId: string, seatId: string) => {
    const currentSelectedTickets = { ...selectedTickets }
    currentSelectedTickets[seatId] = ticketId
    setSelectedTickets(currentSelectedTickets)
  }

  const handleGetTicketBtnClick = async () => {
    setIsAddingToCart(true)
    const ticketsDropdownsData = getTicketDropdownData(
      reservedSeats,
      ticketTypeTierRelationsRef.current
    )
    let selectedSeats = {}
    _forEach(ticketsDropdownsData, ticketData => {
      selectedSeats = {
        ...selectedSeats,
        [ticketData.seatId]: ticketData.ticketsData,
      }
    })
    const addToCartData = getAddToCartRequestData({
      eventId,
      reservations: _map(reservedSeats, seat => seat.seatId),
      selectedSeats,
      selectedTickets,
      guestCounts,
    })
    try {
      const response = await addToCartFunc({
        eventId,
        data: addToCartData,
        ticketQuantity: addToCartData?.attributes?.product_cart_quantity,
      })
      localStorage.removeItem('reservationData')
      onAddToCartSuccess(response)
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const message = _get(error, 'response.data.message', '')
        setError(message)
      }
    } finally {
      setIsAddingToCart(false)
    }
  }

  useEffect(() => {
    if (Date.now() > Number(localStorage.getItem(`reservationStart-${eventId}`))) {
      setSelectedTickets({})
      setReservedSeats([])
    }

    const makeRequests = async () => {
      try {
        setIsLoadingStatuses(true)
        await fetchSeatMapData()
        await fetchSeatMapReservations()
      } catch (error) {
        setError('Something went wrong')
      } finally {
        setIsLoadingStatuses(false)
      }
    }

    makeRequests()

    if (country) {
      localStorage.setItem('eventCountry', country)
    }
  }, [country, eventId, fetchSeatMapData])

  useEffect(() => {
    const intervalId = setInterval(() => {
      fetchSeatMapReservations()
    }, 3000)

    return () => {
      clearInterval(intervalId)
    }
  }, [fetchSeatMapReservations])

  useEffect(() => {
    if (!isOnline) {
      setError(OFFLINE_MESSAGE)
    } else if (isOnline && error === OFFLINE_MESSAGE) {
      setError(null)
    }
  }, [error, isOnline])

  return (
    <>
      {error && (
        <Alert
          severity="error"
          onClose={() => {
            setError(null)
          }}
          variant="filled"
          style={{ width: '350px' }}
        >
          {error}
        </Alert>
      )}
      {showTimer && (
        <Countdown
          date={moment(
            Number(localStorage.getItem(`reservationStart-${eventId}`))
          ).valueOf()}
          renderer={(props: CountdownRenderProps) => (
            <div className="reservation-countdown">
              <span className="reservation-message">{timerMessage}</span>
              <span className="reservation-timer">
                {showZero(props.minutes)}:{showZero(props.seconds)}
              </span>
            </div>
          )}
          onComplete={() => {
            endTimer()
            setSelectedTickets({})
            setReservedSeats([])
            if (onCountdownFinish) {
              onCountdownFinish()
            }
          }}
        />
      )}
      {!isLoadingSeatMapData && !isLoadingStatuses && <ReferralLogic eventId={eventId} />}
      <TicketsSection
        selectedTickets={selectedTickets}
        handleTicketSelect={handleTicketSelect}
        handleCancelReservation={(seatId: string, tireId: string) =>
          handleCancelSeatReservtion(eventId, tireId, seatId)
        }
        handleGetTicketClick={handleGetTicketBtnClick}
        isAddingToCart={isAddingToCart}
        reservedSeats={reservedSeats}
        ticketDeleteButtonContent={ticketDeleteButtonContent}
        ticketInfoContent={ticketInfoContent}
        ticketTypeTierRelations={ticketTypeTierRelationsRef.current}
        currencySymbol={symbol}
        tableMapEnabled={Boolean(tableMapEnabled)}
        guestCounts={guestCounts}
        setGuestCounts={setGuestCounts}
      />
      <SeatMapComponent
        seatMapProps={{
          seatData: seatMapData.seatMap,
          statuses: seatMapStatuses,
          tierPrices: seatMapData.tierPrices,
          seatMapType: seatMapData.seatMapType,
          ticketTypeTierRelations: ticketTypeTierRelationsRef.current,
          seatMapEvents: { onSeatClick },
          isReserving,
          predefinedSeats: seatMapData.predefinedSeats,
          isLoadingSeatMapData,
        }}
        mapContainerId={mapContainerId}
      />
    </>
  )
}
