/**
 * BoostMedia AI Content Generator Admin - Generate Step
 *
 * @package BoostMedia_AI
 * @license GPL-2.0-or-later
 */

import { useState, useEffect, useRef, useCallback } from 'react'
import { Loader2, CheckCircle, XCircle, AlertTriangle, ChevronRight, RefreshCw } from 'lucide-react'
import type { GenerationConfig } from './SelectPostTypeStep'
import type { ContentPlan, GeneratedPost, TopicTreeArticle, TopicTreeCluster, TopicTreePillar } from '../../types'
import { Button, Card } from '../common'
import { endpoints, getErrorMessage } from '../../api/client'
import { t, tf, isRtl } from '../../lib/i18n'

interface GenerateStepProps {
  config: GenerationConfig
  conversation?: {
    technicalSummary?: string
    technicalAnswers?: Record<string, unknown>[]
    contentPlan?: ContentPlan
    contentAnswers?: Record<string, unknown>[]
  }
  onComplete: (posts: GeneratedPost[]) => void
  onBack: () => void
}

type GenerationPhase = 'idle' | 'submitting' | 'polling' | 'success' | 'error' | 'partial'

interface JobState {
  jobId: string
  index: number
}

const POLL_INTERVAL_MS = 5000
const MAX_POLL_ATTEMPTS = 120
const DAILY_ARTICLE_LIMIT = 1000
const MAX_CONSECUTIVE_FAILURES = 3

interface BatchArticleFailure {
  index: number
  error: string
}

interface RetryBatchPayload {
  indices: number[]
  existingPosts: GeneratedPost[]
}

function errorMessageFromUnknown(err: unknown): string {
  return getErrorMessage(err, t('Unknown error'))
}

function isInsufficientCreditsError(err: unknown): boolean {
  if (err && typeof err === 'object') {
    const e = err as Record<string, unknown>
    if (e.status === 402) return true
    if (e.code === 'insufficient_credits') return true
    const msg = String(e.message || '').toLowerCase()
    if (msg.includes('insufficient') && msg.includes('credit')) return true
    if (msg.includes('insufficient credits')) return true
  }
  return false
}

function isSiteBlockedError(err: unknown): boolean {
  if (err && typeof err === 'object') {
    const e = err as Record<string, unknown>
    if (e.status === 403) return true
    if (e.code === 'site_blocked') return true
  }
  return false
}

function isAuthError(err: unknown): boolean {
  if (err && typeof err === 'object') {
    const e = err as Record<string, unknown>
    if (e.status === 401) return true
    if (e.code === 'unauthorized') return true
  }
  return false
}

interface PlannedArticleContext {
  angle: string
  pillarTitle: string
  clusterTitle: string
  clusterSize: number
}

interface GeneratedListResponse {
  items?: GeneratedPost[]
}

interface JobStatusResponse {
  status: string
  generated_post?: GeneratedPost
  error_message?: string
}

function flattenTopicTree(plan?: ContentPlan | null): PlannedArticleContext[] {
  const pillars = plan?.topic_tree?.pillars || []
  const flattened: PlannedArticleContext[] = []

  pillars.forEach((pillar: TopicTreePillar) => {
    const clusters = pillar.clusters || []
    clusters.forEach((cluster: TopicTreeCluster) => {
      const articles = (cluster.articles || []).filter((article: TopicTreeArticle) => Boolean(article.angle?.trim()))
      articles.forEach((article: TopicTreeArticle) => {
        flattened.push({
          angle: article.angle.trim(),
          pillarTitle: pillar.title || '',
          clusterTitle: cluster.title || '',
          clusterSize: articles.length || cluster.article_count || 0,
        })
      })
    })
  })

  return flattened
}

function titleFromPost(post: GeneratedPost): string {
  const contentTitle = typeof post.content === 'object' && post.content && typeof post.content.title === 'string'
    ? post.content.title
    : ''
  return post.title || contentTitle || ''
}

export function GenerateStep({ config, conversation, onComplete, onBack }: GenerateStepProps) {
  const [phase, setPhase] = useState<GenerationPhase>('idle')
  const [currentJob, setCurrentJob] = useState(0)
  const [totalJobs] = useState(config.count)
  const [message, setMessage] = useState(t('Preparing for creation...'))
  const [errorMsg, setErrorMsg] = useState('')
  const [, setJobs] = useState<JobState[]>([])
  const [completedPosts, setCompletedPosts] = useState<GeneratedPost[]>([])
  const [batchFailures, setBatchFailures] = useState<BatchArticleFailure[]>([])
  const abortRef = useRef(false)
  const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const currentJobRef = useRef(0)
  const totalJobsRef = useRef(config.count)
  const completedPostsRef = useRef<GeneratedPost[]>([])
  const retryPayloadRef = useRef<RetryBatchPayload | null>(null)

  useEffect(() => {
    completedPostsRef.current = completedPosts
  }, [completedPosts])

  const cleanup = useCallback(() => {
    abortRef.current = true
    if (pollTimerRef.current) {
      clearTimeout(pollTimerRef.current)
      pollTimerRef.current = null
    }
  }, [])

  const pollJobStatus = useCallback(async (jobId: string, attempt: number): Promise<GeneratedPost | null> => {
    if (abortRef.current) return null

    try {
      const res = await endpoints.getJobStatus(jobId)
      const data = res.data as JobStatusResponse
      const status = data.status

      if (status === 'completed' || status === 'done' || status === 'delivered') {
        if (data.generated_post?.content) {
          return data.generated_post
        }
        const listRes = await endpoints.getGenerated()
        const raw = listRes.data as GeneratedPost[] | GeneratedListResponse
        const items = Array.isArray(raw) ? raw : (raw?.items ?? [])
        const stub = data.generated_post ?? items[0]
        if (stub?.id) {
          try {
            const fullRes = await endpoints.getGeneratedById(stub.id)
            const fullPost = fullRes.data as GeneratedPost
            if (fullPost?.content || fullPost?.generated_content) return fullPost
          } catch { /* fall through to stub */ }
        }
        return stub ?? null
      }

      if (status === 'failed' || status === 'error') {
        const errMsg = data.error_message || t('The job failed on the server')
        throw new Error(errMsg)
      }

      if (status === 'processing' || status === 'queued') {
        setMessage(tf('AI is working on post %d of %d... (status: %s)', currentJobRef.current + 1, totalJobsRef.current, t(status === 'processing' ? 'Processing' : 'Queued')))
      }

      if (attempt >= MAX_POLL_ATTEMPTS) {
        throw new Error(t('Maximum wait time exceeded'))
      }

      await new Promise<void>((resolve) => {
        pollTimerRef.current = setTimeout(resolve, POLL_INTERVAL_MS)
      })

      return pollJobStatus(jobId, attempt + 1)
    } catch (err) {
      if (abortRef.current) return null
      throw err
    }
  }, [])

  const runGeneration = useCallback(async () => {
    abortRef.current = false
    setPhase('submitting')
    setErrorMsg('')

    const retryPayload = retryPayloadRef.current
    retryPayloadRef.current = null
    const isRetryRun = Boolean(retryPayload?.indices?.length)

    if (!isRetryRun) {
      setCompletedPosts([])
      setBatchFailures([])
      setJobs([])
    } else {
      setBatchFailures([])
    }

    let allPosts: GeneratedPost[] = isRetryRun && retryPayload ? [...retryPayload.existingPosts] : []
    if (isRetryRun && retryPayload) {
      setCompletedPosts([...allPosts])
    }

    const runFailures: BatchArticleFailure[] = []
    let consecutiveFailures = 0
    let stoppedConsecutive = false
    let creditExhaustedPartial = false

    try {
      if (!isRetryRun && config.count > DAILY_ARTICLE_LIMIT) {
        setPhase('error')
        setMessage(t('Too many requested posts'))
        setErrorMsg(tf('You can generate up to %d posts per plan.', DAILY_ARTICLE_LIMIT))
        return
      }

      const plannedArticles = flattenTopicTree(conversation?.contentPlan)
      const topicTreeTotal = conversation?.contentPlan?.topic_tree?.total_articles || 0

      let maxRunnablePosts = config.count
      try {
        const creditRes = await endpoints.getCreditsStatus()
        const creditData = creditRes.data as {
          boost_credits?: { remaining: number }
          daily_usage?: {
            generated_today?: number
            limit?: number
            remaining?: number
            articles_generated?: number
            article_limit?: number
            articles_remaining?: number
          }
        }
        const remaining = creditData?.boost_credits?.remaining ?? 0
        const dailyRemaining =
          creditData?.daily_usage?.remaining ??
          creditData?.daily_usage?.articles_remaining
        if (typeof dailyRemaining === 'number') {
          if (dailyRemaining <= 0) {
            setPhase('error')
            setMessage(t('Daily generation limit reached'))
            setErrorMsg(tf('You have already used today\'s limit of %d articles. The limit resets at midnight UTC.', DAILY_ARTICLE_LIMIT))
            return
          }
          maxRunnablePosts = Math.min(maxRunnablePosts, dailyRemaining)
          if (!isRetryRun && dailyRemaining < config.count) {
            setMessage(tf('Daily limit allows %d of %d posts today. We will generate what is still available.', dailyRemaining, config.count))
          }
        }
        if (remaining <= 0) {
          setPhase('error')
          setMessage(t('Insufficient credits'))
          setErrorMsg(t('Your credit balance is too low to generate content. Visit the Usage page to purchase credits.'))
          return
        }
      } catch {
        // Non-fatal: proceed even if credit check fails
      }

      totalJobsRef.current = maxRunnablePosts

      const indicesToRun =
        isRetryRun && retryPayload
          ? [...new Set(retryPayload.indices)].sort((a, b) => a - b)
          : Array.from({ length: maxRunnablePosts }, (_, idx) => idx)

      for (let loopPos = 0; loopPos < indicesToRun.length; loopPos++) {
        if (abortRef.current) break

        if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
          stoppedConsecutive = true
          setErrorMsg(
            t('Generation stopped: too many consecutive errors. You can retry failed articles or try again in a few minutes.'),
          )
          break
        }

        const i = indicesToRun[loopPos]

        setCurrentJob(i)
        currentJobRef.current = i
        setMessage(tf('Sending creation request %d of %d...', i + 1, maxRunnablePosts))
        setPhase('submitting')

        const plannedArticle = plannedArticles[i]
        const recentTitles = allPosts
          .map(titleFromPost)
          .filter(Boolean)
          .slice(-10)

        try {
          let submitRes
          try {
            submitRes = await endpoints.generate({
              post_type: config.postType,
              taxonomy: config.taxonomy || undefined,
              taxonomy_term: config.term || undefined,
              reporter_id: config.reporterId || undefined,
              topic: config.topic,
              keywords: config.keywords,
              length: config.length,
              technical_summary: conversation?.technicalSummary || '',
              technical_answers: conversation?.technicalAnswers || [],
              content_plan: conversation?.contentPlan || {},
              content_answers: conversation?.contentAnswers || [],
              batch_context: {
                article_index: i,
                total_articles: maxRunnablePosts,
                article_angle: plannedArticle?.angle || config.topic,
                pillar_title: plannedArticle?.pillarTitle || '',
                cluster_title: plannedArticle?.clusterTitle || '',
                cluster_size: plannedArticle?.clusterSize || 0,
                topic_tree_total: topicTreeTotal || maxRunnablePosts,
                recent_titles: recentTitles,
              },
            })
          } catch (submitErr) {
            if (isInsufficientCreditsError(submitErr)) {
              if (allPosts.length > 0) {
                creditExhaustedPartial = true
                setPhase('partial')
                setMessage(tf('%d of %d posts created. Generation stopped — credits ran out.', allPosts.length, config.count))
                setErrorMsg(t('Your credit balance is too low to generate more posts. Visit the Usage page to purchase additional credits.'))
                break
              }
              setPhase('error')
              setMessage(t('Insufficient credits'))
              setErrorMsg(t('Your credit balance is too low to generate content. Visit the Usage page to purchase credits.'))
              return
            }
            throw submitErr
          }

          const submitData = submitRes.data as { job_id?: string; status?: string }
          const jobId = submitData.job_id

          if (!jobId) {
            throw new Error(t('No job ID received from server'))
          }

          setJobs((prev) => [...prev, { jobId, index: i }])
          setPhase('polling')
          setMessage(tf('Waiting for post %d of %d results... (AI is working)', i + 1, maxRunnablePosts))

          const post = await pollJobStatus(jobId, 0)

          if (post) {
            allPosts.push(post)
            setCompletedPosts([...allPosts])
            consecutiveFailures = 0
          } else if (!abortRef.current) {
            throw new Error(t('No post was returned after generation'))
          }

          if (loopPos < indicesToRun.length - 1 && !abortRef.current) {
            await new Promise((resolve) => setTimeout(resolve, 1000))
          }
        } catch (err) {
          if (abortRef.current) break

          const errMsg = errorMessageFromUnknown(err)

          if (isInsufficientCreditsError(err)) {
            if (allPosts.length > 0) {
              creditExhaustedPartial = true
              setPhase('partial')
              setMessage(tf('%d of %d posts created. Generation stopped — credits ran out.', allPosts.length, config.count))
              setErrorMsg(t('Your credit balance is too low to generate more posts. Visit the Usage page to purchase additional credits.'))
              break
            }
            setPhase('error')
            setMessage(t('Insufficient credits'))
            setErrorMsg(t('Your credit balance is too low to generate content. Visit the Usage page to purchase credits.'))
            return
          }

          if (isSiteBlockedError(err) || isAuthError(err)) {
            runFailures.push({ index: i, error: errMsg })
            if (allPosts.length > 0) {
              setPhase('partial')
              setMessage(tf('%d of %d articles created successfully', allPosts.length, config.count))
            } else {
              setPhase('error')
              setMessage(t('Error creating content'))
            }
            setErrorMsg(errMsg)
            setBatchFailures(runFailures)
            return
          }

          runFailures.push({ index: i, error: errMsg })
          consecutiveFailures++
          console.log(
            JSON.stringify({
              event: 'batch_article_failed',
              index: i,
              total: maxRunnablePosts,
              error: errMsg,
              consecutive_failures: consecutiveFailures,
              continuing: consecutiveFailures < MAX_CONSECUTIVE_FAILURES,
            }),
          )

          if (loopPos < indicesToRun.length - 1 && !abortRef.current) {
            await new Promise((resolve) => setTimeout(resolve, 1000))
          }
        }
      }

      setBatchFailures(runFailures)

      if (creditExhaustedPartial) {
        return
      }

      if (abortRef.current) {
        return
      }

      if (allPosts.length > 0) {
        if (runFailures.length > 0 || stoppedConsecutive) {
          setPhase('partial')
          setMessage(
            stoppedConsecutive
              ? tf('%d of %d articles created successfully', allPosts.length, config.count)
              : tf('%d of %d articles created successfully', allPosts.length, config.count),
          )
          if (!stoppedConsecutive) {
            setErrorMsg('')
          }
        } else if (allPosts.length < config.count) {
          setPhase('partial')
          setMessage(tf('%d of %d posts created successfully.', allPosts.length, config.count))
          setErrorMsg(tf('This run stopped before all requested posts were submitted. The remaining plan may be limited by credits or the daily cap of %d articles.', DAILY_ARTICLE_LIMIT))
        } else {
          setPhase('success')
          setMessage(tf('%d posts created successfully!', allPosts.length))
        }
      } else if (runFailures.length > 0 || stoppedConsecutive) {
        setPhase('error')
        setMessage(t('Error creating content'))
        if (!stoppedConsecutive) {
          setErrorMsg(runFailures[0]?.error || t('No posts were created'))
        }
      } else {
        setPhase('error')
        setMessage(t('Error creating content'))
        setErrorMsg(t('No posts were created'))
      }
    } catch (err) {
      if (!abortRef.current) {
        if (isInsufficientCreditsError(err) && allPosts.length > 0) {
          setPhase('partial')
          setMessage(tf('%d of %d posts created. Generation stopped — credits ran out.', allPosts.length, config.count))
          setErrorMsg(t('Your credit balance is too low to generate more posts. Visit the Usage page to purchase additional credits.'))
          setBatchFailures(runFailures)
          return
        }
        setPhase('error')
        setMessage(t('Error creating content'))
        setErrorMsg(errorMessageFromUnknown(err))
        setBatchFailures(runFailures)
      }
    }
  }, [config, conversation, pollJobStatus])

  useEffect(() => {
    runGeneration()
    return cleanup
  }, [cleanup, runGeneration])

  const handleRetry = () => {
    cleanup()
    runGeneration()
  }

  const handleRetryFailedArticles = useCallback(() => {
    if (batchFailures.length === 0) return
    cleanup()
    retryPayloadRef.current = {
      indices: batchFailures.map((f) => f.index),
      existingPosts: [...completedPostsRef.current],
    }
    runGeneration()
  }, [batchFailures, cleanup, runGeneration])

  const handleContinue = () => {
    onComplete(completedPosts)
  }

  const progressPercentage =
    phase === 'success'
      ? 100
      : phase === 'partial'
        ? (completedPosts.length / totalJobs) * 100
        : totalJobs > 0
          ? ((currentJob + (phase === 'polling' ? 0.5 : 0)) / totalJobs) * 100
          : 0

  return (
    <div className="max-w-2xl mx-auto">
      <div className="text-center mb-8">
        <h2 className="text-2xl font-bold text-bc-gray-800 mb-2">
          {(phase === 'submitting' || phase === 'polling') && t('Creating content...')}
          {phase === 'success' && t('Creation completed!')}
          {phase === 'partial' && t('Partially completed')}
          {phase === 'error' && t('Creation error')}
          {phase === 'idle' && t('Preparing...')}
        </h2>
        <p className="text-bc-gray-500">{message}</p>
      </div>

      <Card className="mb-8">
        {/* Progress Bar */}
        <div className="mb-6">
          <div className="flex justify-between text-sm text-bc-gray-600 mb-2">
            <span>{t('Progress')}</span>
            <span>
              {completedPosts.length} / {totalJobs}
            </span>
          </div>
          <div className="h-3 bg-bc-gray-200 rounded-full overflow-hidden">
            <div
              className={`
                h-full transition-all duration-500 ease-out rounded-full
                ${phase === 'error' ? 'bg-bc-error' : phase === 'partial' ? 'bg-yellow-500' : 'bg-bc-primary'}
              `}
              style={{ width: `${progressPercentage}%` }}
            />
          </div>
        </div>

        {/* Status Icon */}
        <div className="flex flex-col items-center py-8">
          {(phase === 'submitting' || phase === 'polling') && (
            <div className="relative">
              <div className="w-24 h-24 rounded-full bg-bc-primary-light flex items-center justify-center">
                <Loader2 className="w-12 h-12 text-bc-primary animate-spin" />
              </div>
              <div className="absolute -bottom-2 -right-2 w-8 h-8 rounded-full bg-white shadow-md flex items-center justify-center">
                <span className="text-sm font-bold text-bc-primary">
                  {currentJob + 1}
                </span>
              </div>
            </div>
          )}

          {phase === 'success' && (
            <div className="w-24 h-24 rounded-full bg-green-50 flex items-center justify-center">
              <CheckCircle className="w-12 h-12 text-bc-success" />
            </div>
          )}

          {phase === 'partial' && (
            <div className="w-24 h-24 rounded-full bg-yellow-50 flex items-center justify-center">
              <AlertTriangle className="w-12 h-12 text-yellow-500" />
            </div>
          )}

          {phase === 'error' && (
            <div className="w-24 h-24 rounded-full bg-red-50 flex items-center justify-center">
              <XCircle className="w-12 h-12 text-bc-error" />
            </div>
          )}
        </div>

        {/* Error / Credit Warning Details */}
        {phase === 'error' && errorMsg && (
          <div className="mt-4 p-4 bg-red-50 rounded-bc border border-red-100">
            <p className="text-bc-error text-sm">{errorMsg}</p>
          </div>
        )}
        {phase === 'partial' && errorMsg && (
          <div className="mt-4 p-4 bg-yellow-50 rounded-bc border border-yellow-200">
            <p className="text-yellow-700 text-sm">{errorMsg}</p>
          </div>
        )}

        {/* Polling info */}
        {phase === 'polling' && (
          <div className="mt-4 text-center">
            <p className="text-sm text-bc-gray-500">
              {t('Topic:')} <span className="font-medium text-bc-gray-700">{config.topic}</span>
            </p>
            <p className="text-xs text-bc-gray-400 mt-2">
              {t('The process may take up to a minute. We check the status every few seconds...')}
            </p>
            {config.keywords.length > 0 && (
              <p className="text-sm text-bc-gray-500 mt-1">
                {t('Keywords:')}{' '}
                <span className="font-medium text-bc-gray-700">
                  {config.keywords.join(', ')}
                </span>
              </p>
            )}
          </div>
        )}

        {/* Success Summary */}
        {phase === 'success' && (
          <div className="mt-4 text-center">
            <p className="text-bc-gray-600">
              {t('Created')}{' '}
              <span className="font-bold text-bc-success">
                {completedPosts.length}
              </span>{' '}
              {t('new posts')}
            </p>
          </div>
        )}

        {/* Partial Success Summary */}
        {phase === 'partial' && (
          <div className="mt-4 text-center">
            <p className="text-bc-gray-600">
              {t('Created')}{' '}
              <span className="font-bold text-yellow-600">
                {completedPosts.length}
              </span>{' '}
              {tf('of %d requested posts', totalJobs)}
            </p>
          </div>
        )}

        {batchFailures.length > 0 && (phase === 'partial' || phase === 'error') && (
          <div className="bc-failures-list mt-6 text-start border border-bc-gray-200 rounded-bc p-4 bg-bc-gray-50">
            <h4 className="text-sm font-semibold text-bc-gray-800 mb-2">
              {tf('%d articles failed:', batchFailures.length)}
            </h4>
            <ul className="space-y-1 list-none p-0 m-0">
              {batchFailures.map((f) => (
                <li key={f.index} className="text-sm text-bc-error">
                  {tf('Article %d: %s', f.index + 1, f.error)}
                </li>
              ))}
            </ul>
          </div>
        )}
      </Card>

      {/* Navigation */}
      <div className="flex flex-wrap justify-between gap-3 items-center">
        <Button
          variant="ghost"
          onClick={() => { cleanup(); onBack() }}
          disabled={phase === 'submitting' || phase === 'polling' || phase === 'idle'}
          icon={<ChevronRight className="w-5 h-5 ltr:rotate-180" />}
          iconPosition={isRtl() ? 'end' : 'start'}
        >
          {t('Back')}
        </Button>

        <div className="flex flex-wrap gap-2 justify-end">
          {phase === 'error' && (
            <Button
              variant="secondary"
              onClick={handleRetry}
              icon={<RefreshCw className="w-5 h-5" />}
            >
              {t('Try again')}
            </Button>
          )}

          {batchFailures.length > 0 && (phase === 'partial' || phase === 'error') && (
            <Button
              variant="secondary"
              onClick={handleRetryFailedArticles}
              icon={<RefreshCw className="w-5 h-5" />}
            >
              {t('Retry failed articles')}
            </Button>
          )}

          {phase === 'partial' && completedPosts.length > 0 && (
            <Button onClick={handleContinue} size="lg">
              {tf('Review %d created articles', completedPosts.length)}
            </Button>
          )}

          {phase === 'success' && (
            <Button onClick={handleContinue} size="lg">
              {t('Continue to review')}
            </Button>
          )}
        </div>
      </div>
    </div>
  )
}
