import { CampaignStatuses } from '@komi-app/components'
import { Time } from '@komi-app/creator-ui'
import { add, formatISO, set } from 'date-fns'
import { PaginationQueryParameters, usePagination } from 'hooks/usePagination'
import { CampaignsWithCursorTableRow } from 'pages/AudienceAndCampaigns/typesUtils'
import { campaignService } from 'services'
import { toCampaignStatuses } from 'utils/dca/campaigns'
import isNil from 'lodash/isNil'
import {
  CampaignScheduleType,
  EMAIL_INITIAL_GROWTH_RATE,
  oneDayInMs,
  oneHourInMs
} from '@komi-app/messaging-sdk'

async function fetchTableRows(
  queryParams: PaginationQueryParameters,
  talentProfileId: string,
  searchTerm: string
) {
  const results = await campaignService.getCampaignsWithCursor(
    queryParams,
    talentProfileId,
    searchTerm
  )
  const mappedResult = results?.map<CampaignsWithCursorTableRow>(c => {
    const status = toCampaignStatuses(c.status, c.schedule.startTimestamp)

    return {
      campaignId: c.campaignId,
      name: c.name,
      status,
      segmentData: c.segments,
      openRate: c.openRate,
      clickThroughRate: c.clickThroughRate,
      scheduledSendTimestamp: c.schedule.startTimestamp ?? null,
      cursor: c.cursor
    }
  })
  return mappedResult ?? []
}

/**
 * Function expects a unix timestamp
 * and then compares it to the current time and returns true if the timestamp is in the past
 * @param UnixTimeStamp number
 * @returns boolean
 */
const isPastDateTime = (UnixTimeStamp: number): boolean => {
  return new Date(UnixTimeStamp).getTime() < Date.now()
}

/**
 * Some statuses are always approved to edit and are not subject
 * to the 10 minute sendgrid cutoff point.
 */
export const statusesAlwaysApprovedToEdit = [
  CampaignStatuses.DRAFT,
  CampaignStatuses.STOPPED
]

export function useCampaignsList(
  talentProfileId: string | undefined,
  searchTerm: string
) {
  return usePagination(
    'campaigns',
    async queryParams => {
      if (!talentProfileId) {
        return []
      }

      return (
        (await fetchTableRows(queryParams, talentProfileId, searchTerm)) ?? []
      )
    },
    async countParams => {
      if (!talentProfileId) {
        return 0
      }

      return (
        (await campaignService.getCount(
          countParams,
          talentProfileId,
          searchTerm
        )) ?? 0
      )
    },
    10,
    'DESC',
    [talentProfileId, searchTerm]
  )
}

// Calculates the campaign scheduled time with date based on the provided date and time values.
export function calculateScheduleDateTime(
  scheduledDate: Date | undefined,
  scheduledTime: Partial<Time>
): Date | null {
  if (
    isNil(scheduledDate) ||
    isNil(scheduledTime.hour) ||
    isNil(scheduledTime.minute) ||
    isNil(scheduledTime.period)
  ) {
    return null
  }

  const { minute } = scheduledTime
  const hour = get24HourTime(scheduledTime)

  return set(scheduledDate, { hours: hour, minutes: minute })
}

function get24HourTime(time: Partial<Time>): number {
  const { hour, period } = time
  if (!hour || !period) {
    return 0
  }

  if (period === 'AM' && hour === 12) {
    return 0
  }

  if (period === 'PM' && hour === 12) {
    return 12
  }

  return period === 'AM' ? hour : hour + 12
}

export function calculateSchedulePreviewTableData({
  startDate,
  totalRecipients,
  growthRate,
  initialDailySends,
  scheduleType
}: {
  startDate: Date | undefined
  totalRecipients: number | undefined
  growthRate: number
  initialDailySends: number
  scheduleType: CampaignScheduleType
}) {
  if (!totalRecipients) {
    return []
  }

  if (scheduleType === CampaignScheduleType.ALL_AT_ONCE) {
    return [
      {
        dailySends: totalRecipients,
        date: formatDateISO(startDate),
        sendsToDate: totalRecipients
      }
    ]
  }

  const tableData = []
  let sendsToDate = 0
  let currentDate = startDate

  while (sendsToDate < totalRecipients) {
    const dailySends = Math.round(
      initialDailySends * growthRate ** tableData.length
    )

    const isLastSubset = sendsToDate + dailySends > totalRecipients
    if (isLastSubset) {
      const remainingSends = totalRecipients - sendsToDate
      tableData.push({
        dailySends: remainingSends,
        date: formatDateISO(currentDate),
        sendsToDate: totalRecipients
      })
      break
    }

    sendsToDate = sendsToDate + dailySends

    tableData.push({
      dailySends,
      date: formatDateISO(currentDate),
      sendsToDate
    })

    currentDate = currentDate ? add(currentDate, { days: 1 }) : undefined
  }

  return tableData
}

export const calculateEstimatedCompletionDate = ({
  startDate,
  totalRecipients,
  initialDailySends,
  scheduleType
}: {
  startDate: Date | undefined
  totalRecipients: number | undefined
  initialDailySends: number
  scheduleType: CampaignScheduleType
}) => {
  if (!startDate || !totalRecipients) {
    return undefined
  }

  const estimatedInterval =
    scheduleType === CampaignScheduleType.ALL_AT_ONCE ? oneHourInMs : oneDayInMs

  // N = s * r^x
  // N/s = r^x
  // ln(N/s) = x * ln(r)
  // x = ln(N/s) / ln(r)
  const units =
    Math.log(totalRecipients / initialDailySends) /
    Math.log(EMAIL_INITIAL_GROWTH_RATE)
  const integerUnits = Math.ceil(units)

  return add(startDate, {
    seconds: (estimatedInterval * integerUnits) / 1000
  })
}

/**
 * Formats a date to a string
 *
 * @example
 * formatDateISO(new Date()) // '2024-10-14'
 *
 * @param date The date to format
 */
function formatDateISO(date: Date | undefined) {
  if (!date) {
    return undefined
  }
  return formatISO(date, { representation: 'date' })
}
