import {
  getDeleteOldCronJobRunDetailsByCtidSql,
  getJobRunDetailsPageCountSql,
} from '@supabase/pg-meta'
import { useCallback, useRef, useState } from 'react'
import { toast } from 'sonner'

import { CLEANUP_INTERVALS } from './CronJobsTab.constants'
import type { ConnectionVars } from '@/data/common.types'
import {
  CTID_BATCH_PAGE_SIZE,
  validatePageNumber,
} from '@/data/database-cron-jobs/database-cron-jobs.utils'
import {
  getDeleteOldCronJobRunDetailsByCtidKey,
  getJobRunDetailsPageCountKey,
} from '@/data/database-cron-jobs/keys'
import { useScheduleCronJobRunDetailsCleanupMutation } from '@/data/database-cron-jobs/schedule-clean-up-mutation'
import { useExecuteSqlMutation } from '@/data/sql/execute-sql-mutation'

// Delay between batches to allow other queries to proceed (in milliseconds)
const BATCH_DELAY_MS = 100

type UseCronJobsCleanupActionsOptions = ConnectionVars

export interface BatchDeletionProgress {
  currentBatch: number
  totalBatches: number
  totalRowsDeleted: number
}

export type CleanupState =
  | { status: 'idle' }
  | { status: 'deleting'; progress: BatchDeletionProgress }
  | { status: 'delete-success'; totalRowsDeleted: number }
  | { status: 'delete-error'; error: string }

export const useCronJobsCleanupActions = ({
  projectRef,
  connectionString,
}: UseCronJobsCleanupActionsOptions) => {
  const [cleanupInterval, setCleanupInterval] = useState(CLEANUP_INTERVALS[0].value)
  const [cleanupState, setCleanupState] = useState<CleanupState>({ status: 'idle' })

  // Ref to track cancellation
  const cancelledRef = useRef(false)

  const { mutateAsync: executeSql } = useExecuteSqlMutation({
    onError: () => {}, // Error handled inline
  })

  const {
    mutate: scheduleCronJobCleanup,
    isPending: isScheduling,
    isSuccess: isScheduleSuccess,
  } = useScheduleCronJobRunDetailsCleanupMutation()

  /**
   * Run batched deletion using ctid ranges.
   * This approach scans the table in page chunks to avoid:
   * - Buffer cache pollution from full table scans
   * - Long-running transactions that block vacuum
   * - Lock accumulation from deleting millions of rows at once
   */
  const runBatchedDeletion = useCallback(
    async (interval: string) => {
      if (!projectRef) {
        console.error('[CronJobsTab > batch deletion] Project reference is required')
        toast.error('There was an error running the cleanup. Please try again.')
        return
      }

      cancelledRef.current = false

      try {
        // Step 1: Get the total number of pages in the table
        setCleanupState({
          status: 'deleting',
          progress: { currentBatch: 0, totalBatches: 0, totalRowsDeleted: 0 },
        })

        const pageCountResult = await executeSql({
          projectRef,
          connectionString,
          sql: getJobRunDetailsPageCountSql(),
          queryKey: getJobRunDetailsPageCountKey(projectRef),
        })

        const rawTotalPages = pageCountResult.result?.[0]?.num_pages ?? 0
        const totalPages = Number(rawTotalPages)
        if (!Number.isFinite(totalPages) || totalPages < 0) {
          throw new Error(
            `[CronJobs > cleanup actions] Invalid page count returned: ${rawTotalPages}`
          )
        }

        if (totalPages === 0) {
          setCleanupState({ status: 'delete-success', totalRowsDeleted: 0 })
          toast.success('The job_run_details table is empty.')
          return
        }

        const totalBatches = Math.ceil(totalPages / CTID_BATCH_PAGE_SIZE)
        let totalRowsDeleted = 0

        // Step 2: Iterate through pages in batches
        for (let batch = 0; batch < totalBatches; batch++) {
          // Check for cancellation
          if (cancelledRef.current) {
            setCleanupState({ status: 'idle' })
            toast.info('Deletion cancelled.')
            return
          }

          const startPage = batch * CTID_BATCH_PAGE_SIZE
          const endPage = Math.min((batch + 1) * CTID_BATCH_PAGE_SIZE, totalPages + 1)

          validatePageNumber(startPage, 'startPage')
          validatePageNumber(endPage, 'endPage')

          setCleanupState({
            status: 'deleting',
            progress: {
              currentBatch: batch + 1,
              totalBatches,
              totalRowsDeleted,
            },
          })

          const deleteResult = await executeSql({
            projectRef,
            connectionString,
            sql: getDeleteOldCronJobRunDetailsByCtidSql(interval, startPage, endPage),
            queryKey: getDeleteOldCronJobRunDetailsByCtidKey(projectRef, interval, startPage),
          })

          const deletedCount = deleteResult.result?.[0]?.deleted_count ?? 0
          totalRowsDeleted += deletedCount

          if (cancelledRef.current) {
            setCleanupState({ status: 'idle' })
            toast.info('Deletion cancelled.')
            return
          }

          if (batch < totalBatches - 1) {
            await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY_MS))
          }
        }

        setCleanupState({ status: 'delete-success', totalRowsDeleted })
        toast.success(
          `Deleted ${totalRowsDeleted.toLocaleString()} cron job runs older than ${interval}.`
        )
      } catch (error) {
        console.error('[CronJobs] Batch deletion failed with error: %O', error)
        const errorMessage = error instanceof Error ? error.message : 'Unknown error'
        setCleanupState({ status: 'delete-error', error: errorMessage })
        toast.error('Running the cleanup failed. Please try again.')
      }
    },
    [projectRef, connectionString, executeSql]
  )

  /**
   * Schedule a daily cleanup job.
   * This should only be called after a successful initial deletion.
   */
  const scheduleCleanup = useCallback(
    async ({ interval, onSuccess }: { interval: string; onSuccess?: () => void }) => {
      if (!projectRef) {
        console.error('[CronJobsTab > schedule cleanup] Project reference is required')
        toast.error('There was an error scheduling the cleanup. Please try again.')
        return
      }

      scheduleCronJobCleanup(
        { projectRef, connectionString, interval },
        {
          onSuccess: () => {
            toast.success('Scheduled daily cleanup job.')
            onSuccess?.()
          },
        }
      )
    },
    [connectionString, projectRef, scheduleCronJobCleanup]
  )

  /**
   * Cancel an in-progress deletion.
   */
  const cancelDeletion = useCallback(() => {
    cancelledRef.current = true
    setCleanupState({ status: 'idle' })
  }, [])

  return {
    cleanupInterval,
    cleanupState,
    isScheduling,
    isScheduleSuccess,
    setCleanupInterval,
    runBatchedDeletion,
    scheduleCleanup,
    cancelDeletion,
  }
}
