'use client'

import { useCallback, useEffect, useMemo, useState } from 'react'
import { cmsApi } from '../../lib/api.js'

/** Editable + computed shape of the General tab form. */
export interface GeneralSettingsForm {
  siteTitle: string
  tagline: string
  siteUrl: string
  language: string
  timezone: string
  defaultNoIndex: boolean
  defaultNoFollow: boolean
  noIndexNonProduction: boolean
}

export type SettingsSource = 'config' | 'environment' | 'database' | 'plugin' | 'computed'

export interface SettingsSourceMetadata {
  source: SettingsSource
  editable: boolean
  reason?: string
}

export type IndexStatusLevel =
  | 'indexable'
  | 'protected'
  | 'blocked'
  | 'partially_blocked'
  | 'unknown'

export interface SiteIndexStatus {
  status: IndexStatusLevel
  severity: 'success' | 'warning' | 'danger' | 'neutral'
  label: string
  description: string
  environment: string
  checks: Array<{
    key: string
    label: string
    value: string
    status: 'pass' | 'warning' | 'fail' | 'unknown'
  }>
  actions: Array<{ label: string; href?: string; action?: string }>
  lastAuditAt: string | null
}

export interface ValidationIssue {
  /** Field key the issue relates to. A plain string so the primitive can be
   * reused by other settings tabs (e.g. Appearance) whose fields aren't part
   * of `GeneralSettingsForm`. */
  field: string
  message: string
  severity: 'error' | 'warning'
}

const EMPTY_FORM: GeneralSettingsForm = {
  siteTitle: '',
  tagline: '',
  siteUrl: '',
  language: 'en',
  timezone: 'UTC',
  defaultNoIndex: false,
  defaultNoFollow: false,
  noIndexNonProduction: false,
}

/** Validate a site URL: absolute http(s), no trailing slash, parseable. */
export function validateSiteUrl(value: string): { valid: boolean; error?: string } {
  const trimmed = value.trim()
  if (!trimmed) return { valid: true }
  let url: URL
  try {
    url = new URL(trimmed)
  } catch {
    return { valid: false, error: 'Enter a valid absolute URL, e.g. https://example.com' }
  }
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    return { valid: false, error: 'Site URL must start with http:// or https://' }
  }
  if (trimmed.endsWith('/')) {
    return { valid: false, error: 'Remove the trailing slash (e.g. https://example.com)' }
  }
  return { valid: true }
}

/** Best-effort IANA timezone check using the Intl API. */
export function isValidTimeZone(tz: string): boolean {
  if (!tz) return false
  try {
    new Intl.DateTimeFormat('en-US', { timeZone: tz })
    return true
  } catch {
    return false
  }
}

/** Best-effort BCP 47 locale check. */
export function isValidLocale(locale: string): boolean {
  if (!locale) return false
  try {
    // Throws RangeError on structurally invalid tags.
    new Intl.Locale(locale)
    return /^[a-zA-Z]{2,3}([-_][a-zA-Z0-9]{2,8})*$/.test(locale)
  } catch {
    return false
  }
}

function validate(form: GeneralSettingsForm, isProduction: boolean): ValidationIssue[] {
  const issues: ValidationIssue[] = []
  if (!form.siteTitle.trim()) {
    issues.push({ field: 'siteTitle', message: 'Site Title is required.', severity: 'error' })
  }
  const urlCheck = validateSiteUrl(form.siteUrl)
  if (!urlCheck.valid) {
    issues.push({ field: 'siteUrl', message: urlCheck.error!, severity: 'error' })
  } else if (!form.siteUrl.trim() && isProduction) {
    issues.push({
      field: 'siteUrl',
      message: 'Site URL is not set — canonical, sitemap, and Open Graph URLs may be invalid.',
      severity: 'warning',
    })
  }
  if (!isValidLocale(form.language)) {
    issues.push({
      field: 'language',
      message: 'Language must be a valid locale code, e.g. en-US.',
      severity: 'error',
    })
  }
  if (!isValidTimeZone(form.timezone)) {
    issues.push({
      field: 'timezone',
      message: 'Timezone must be a valid IANA name, e.g. America/Los_Angeles.',
      severity: 'error',
    })
  }
  return issues
}

interface SeoConfigResponse {
  static?: { site?: Record<string, any> | null }
  overrides?: { site?: Record<string, any> | null } | null
  effective?: { site?: Record<string, any> | null }
}

type SaveState = 'idle' | 'saving' | 'success' | 'error'

export interface UseGeneralSettings {
  loading: boolean
  loadError: string | null
  form: GeneralSettingsForm
  setField: <K extends keyof GeneralSettingsForm>(key: K, value: GeneralSettingsForm[K]) => void
  dirty: boolean
  issues: ValidationIssue[]
  hasErrors: boolean
  indexStatus: SiteIndexStatus | null
  sources: Record<string, SettingsSourceMetadata>
  isProduction: boolean
  saveState: SaveState
  saveError: string | null
  save: () => Promise<void>
  reset: () => void
  refetch: () => void
}

/**
 * Loads the General tab from its three real backends — the `settings` global
 * (site info / locale), the shared `/seo/config` (site URL + robots defaults),
 * and the computed `/seo/index-status` — tracks a dirty diff, and on save
 * writes each field back to its OWNING store (never a duplicate), preserving
 * unrelated keys.
 */
export function useGeneralSettings(canEdit: boolean): UseGeneralSettings {
  const [loading, setLoading] = useState(true)
  const [loadError, setLoadError] = useState<string | null>(null)
  const [baseline, setBaseline] = useState<GeneralSettingsForm>(EMPTY_FORM)
  const [form, setForm] = useState<GeneralSettingsForm>(EMPTY_FORM)
  const [indexStatus, setIndexStatus] = useState<SiteIndexStatus | null>(null)
  const [sources, setSources] = useState<Record<string, SettingsSourceMetadata>>({})
  // Full settings-global blob so a partial save never wipes AI/security keys.
  const [settingsBlob, setSettingsBlob] = useState<Record<string, unknown>>({})
  const [overrideSite, setOverrideSite] = useState<Record<string, any>>({})
  const [saveState, setSaveState] = useState<SaveState>('idle')
  const [saveError, setSaveError] = useState<string | null>(null)
  const [reloadKey, setReloadKey] = useState(0)

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setLoadError(null)
    Promise.all([
      cmsApi<Record<string, any>>('/globals/settings'),
      cmsApi<SeoConfigResponse>('/seo/config'),
      cmsApi<SiteIndexStatus>('/seo/index-status'),
    ])
      .then(([settingsRes, seoRes, statusRes]) => {
        if (cancelled) return
        if (settingsRes.error && settingsRes.status !== 404) {
          setLoadError(settingsRes.error)
        }
        const settingsData = settingsRes.data ?? {}
        const seo = seoRes.data ?? {}
        const effectiveSite = seo.effective?.site ?? {}
        const staticSite = seo.static?.site ?? {}
        const overSite = seo.overrides?.site ?? {}
        const robots = effectiveSite.robots ?? {}

        const loaded: GeneralSettingsForm = {
          siteTitle: String(settingsData.siteTitle ?? ''),
          tagline: String(settingsData.tagline ?? ''),
          siteUrl: String(effectiveSite.siteUrl ?? settingsData.siteUrl ?? ''),
          language: String(settingsData.language ?? 'en'),
          timezone: String(settingsData.timezone ?? 'UTC'),
          defaultNoIndex: robots.defaultNoIndex === true,
          defaultNoFollow: robots.defaultNoFollow === true,
          noIndexNonProduction: robots.noIndexNonProduction === true,
        }

        // Site URL is config-managed when actuate.config.ts supplies it.
        const siteUrlFromConfig = Boolean(staticSite.siteUrl)
        setSources({
          siteUrl: siteUrlFromConfig
            ? { source: 'config', editable: false, reason: 'Set in actuate.config.ts' }
            : { source: 'database', editable: canEdit },
          siteTitle: { source: 'database', editable: canEdit },
          tagline: { source: 'database', editable: canEdit },
          language: { source: 'database', editable: canEdit },
          timezone: { source: 'database', editable: canEdit },
          seoDefaults: {
            source: 'plugin',
            editable: canEdit,
            reason: 'Shared with the SEO section',
          },
        })

        setBaseline(loaded)
        setForm(loaded)
        setSettingsBlob(settingsData)
        setOverrideSite(overSite ?? {})
        if (!seoRes.error && statusRes.data) setIndexStatus(statusRes.data)
      })
      .catch(() => {
        if (!cancelled) setLoadError('Failed to load settings.')
      })
      .finally(() => {
        if (!cancelled) setLoading(false)
      })
    return () => {
      cancelled = true
    }
  }, [reloadKey, canEdit])

  const setField = useCallback(
    <K extends keyof GeneralSettingsForm>(key: K, value: GeneralSettingsForm[K]) => {
      setForm((prev) => ({ ...prev, [key]: value }))
      setSaveState('idle')
    },
    [],
  )

  const isProduction = (indexStatus?.environment ?? '') === 'production'

  const dirty = useMemo(
    () =>
      (Object.keys(form) as Array<keyof GeneralSettingsForm>).some((k) => form[k] !== baseline[k]),
    [form, baseline],
  )

  const issues = useMemo(() => validate(form, isProduction), [form, isProduction])
  const hasErrors = issues.some((i) => i.severity === 'error')

  const refetchStatus = useCallback(async () => {
    const res = await cmsApi<SiteIndexStatus>('/seo/index-status')
    if (!res.error && res.data) setIndexStatus(res.data)
  }, [])

  const save = useCallback(async () => {
    if (!canEdit || hasErrors) return
    setSaveState('saving')
    setSaveError(null)

    const siteUrlEditable = sources.siteUrl?.editable !== false
    const siteInfoChanged =
      form.siteTitle !== baseline.siteTitle ||
      form.tagline !== baseline.tagline ||
      form.language !== baseline.language ||
      form.timezone !== baseline.timezone
    const siteUrlChanged = siteUrlEditable && form.siteUrl !== baseline.siteUrl
    const robotsChanged =
      form.defaultNoIndex !== baseline.defaultNoIndex ||
      form.defaultNoFollow !== baseline.defaultNoFollow ||
      form.noIndexNonProduction !== baseline.noIndexNonProduction

    try {
      // 1. Site info → the `settings` global (preserve AI/security/layout keys,
      //    and drop legacy robots/siteUrl duplicates so SEO config owns them).
      //    Read-modify-write against a fresh fetch so a concurrent save from the
      //    Security/AI tabs (same global blob) is never clobbered.
      if (siteInfoChanged) {
        const current = await cmsApi<Record<string, unknown>>('/globals/settings')
        const blob = { ...(current.data ?? settingsBlob) }
        delete blob.defaultNoIndex
        delete blob.defaultNoFollow
        delete blob.noIndexNonProduction
        delete blob.siteUrl
        const payload = {
          ...blob,
          siteTitle: form.siteTitle.trim(),
          tagline: form.tagline.trim(),
          language: form.language,
          timezone: form.timezone,
        }
        const res = await cmsApi('/globals/settings', {
          method: 'PUT',
          body: JSON.stringify(payload),
        })
        if (res.error) throw new Error(res.error)
        setSettingsBlob(payload)
      }

      // 2. Site URL + robots defaults → the SHARED SEO config (merge over the
      //    current override site so SEO-tab fields aren't lost).
      if (siteUrlChanged || robotsChanged) {
        const nextSite: Record<string, any> = { ...overrideSite }
        if (siteUrlChanged) nextSite.siteUrl = form.siteUrl.trim() || undefined
        if (robotsChanged) {
          nextSite.robots = {
            ...(overrideSite.robots ?? {}),
            defaultNoIndex: form.defaultNoIndex,
            defaultNoFollow: form.defaultNoFollow,
            noIndexNonProduction: form.noIndexNonProduction,
          }
        }
        const res = await cmsApi('/seo/config', {
          method: 'PUT',
          body: JSON.stringify({ site: nextSite }),
        })
        if (res.error) throw new Error(res.error)
        setOverrideSite(nextSite)
      }

      setBaseline(form)
      setSaveState('success')
      await refetchStatus()
    } catch (err) {
      setSaveState('error')
      setSaveError(err instanceof Error ? err.message : 'Failed to save settings.')
    }
  }, [canEdit, hasErrors, form, baseline, settingsBlob, overrideSite, sources, refetchStatus])

  const reset = useCallback(() => {
    setForm(baseline)
    setSaveState('idle')
    setSaveError(null)
  }, [baseline])

  const refetch = useCallback(() => setReloadKey((k) => k + 1), [])

  return {
    loading,
    loadError,
    form,
    setField,
    dirty,
    issues,
    hasErrors,
    indexStatus,
    sources,
    isProduction,
    saveState,
    saveError,
    save,
    reset,
    refetch,
  }
}
