/**
 * SEO service — typed client over the cms-core SEO REST endpoints.
 *
 * Every SEO admin component reads/writes through this module (never raw
 * `cmsApi` / `fetch`), so the network contract lives in one place. Fetchers
 * return typed data with safe fallbacks; mutations return `{ error?: string }`.
 *
 * Endpoint paths here are the contract implemented by cms-core
 * `packages/cms-core/src/api/handlers.ts` across phases 2-3 and 6.
 */
import { cmsApi } from './api.js'

/**
 * `cmsApi` never throws — it returns `{ error, status }` on a network/HTTP
 * failure. Read (GET) fetchers below must re-surface that as a thrown error so
 * `useSeoResource` can distinguish "request failed" from "empty result".
 * Without this, a failed `/seo/overview` would fall through to the empty
 * fallback (a fake score of 0 / grade "Critical") and the error/retry UI would
 * be permanently unreachable. Mutations intentionally return `{ error }` and do
 * not use this.
 */
function throwIfError(res: { error?: string }): void {
  if (res.error) throw new Error(res.error)
}

// ─── Shared enums ────────────────────────────────────────────────────

export type SeoGrade = 'good' | 'fair' | 'poor' | 'critical'
export type SeoSeverity = 'critical' | 'warning' | 'info'
export type SeoIssueStatus = 'open' | 'ignored' | 'resolved' | 'recurring'
export type SeoIssueCategory =
  | 'content'
  | 'technical'
  | 'redirects'
  | 'media'
  | 'performance'
  | 'schema'
  | 'social'
  | 'indexing'
export type RedirectStatusCode = 301 | 302 | 307 | 308 | 410

// ─── Overview ────────────────────────────────────────────────────────

export interface SeoSiteScore {
  score: number
  grade: SeoGrade
  /** Change vs the previous audit run, e.g. +6. */
  delta: number | null
  breakdown: { category: string; score: number; weight: number; contribution: number }[]
}

export interface SeoMetric {
  label: string
  value: string
  helper: string
  /** Optional trend direction for the helper colour. */
  trend?: 'up' | 'down' | 'flat'
}

export interface SeoIssue {
  id: string
  auditRunId: string | null
  entityType: string | null
  entityId: string | null
  entityTitle: string | null
  url: string | null
  severity: SeoSeverity
  category: SeoIssueCategory
  type: string
  title: string
  description: string | null
  impact: string | null
  recommendation: string | null
  status: SeoIssueStatus
  fixable: boolean
  fixActionType: string | null
  relatedEntityIds: string[]
  source: string
  firstDetectedAt: string | null
  lastDetectedAt: string | null
  resolvedAt: string | null
}

export interface QuickWin {
  id: string
  title: string
  explanation: string
  impact: 'High' | 'Medium' | 'Low'
  issueId: string | null
  fixActionType: string | null
}

export interface SearchPerformanceRow {
  url: string
  title: string
  impressions: number
  clicks: number
  ctr: number
  averagePosition: number
}

export interface SeoOverview {
  siteScore: SeoSiteScore
  metrics: SeoMetric[]
  issues: SeoIssue[]
  issuesSummary: { total: number; critical: number; warning: number; info: number }
  quickWins: QuickWin[]
  searchPerformance: SearchPerformanceRow[]
  searchConsoleConnected: boolean
  lastAuditRunId: string | null
}

const EMPTY_OVERVIEW: SeoOverview = {
  siteScore: { score: 0, grade: 'critical', delta: null, breakdown: [] },
  metrics: [],
  issues: [],
  issuesSummary: { total: 0, critical: 0, warning: 0, info: 0 },
  quickWins: [],
  searchPerformance: [],
  searchConsoleConnected: false,
  lastAuditRunId: null,
}

export async function fetchSeoOverview(): Promise<SeoOverview> {
  const res = await cmsApi<SeoOverview>('/seo/overview')
  throwIfError(res)
  return res.data ?? EMPTY_OVERVIEW
}

export async function fetchSearchPerformance(
  params: {
    limit?: number
  } = {},
): Promise<SearchPerformanceRow[]> {
  const qs = params.limit ? `?limit=${params.limit}` : ''
  const res = await cmsApi<{ rows: SearchPerformanceRow[] }>(`/seo/search-performance${qs}`)
  throwIfError(res)
  return res.data?.rows ?? []
}

// ─── Content SEO ─────────────────────────────────────────────────────

export interface SeoContentRecord {
  id: string
  entityType: 'page' | 'post'
  entityId: string
  title: string
  url: string
  canonicalUrl: string | null
  metaTitle: string | null
  metaDescription: string | null
  focusKeyword: string | null
  ogTitle: string | null
  ogDescription: string | null
  ogImageId: string | null
  ogImage: string | null
  twitterTitle: string | null
  twitterDescription: string | null
  twitterImageId: string | null
  noindex: boolean
  nofollow: boolean
  structuredDataType: string | null
  structuredData: Record<string, unknown> | null
  seoScore: number
  seoGrade: SeoGrade
  updatedAt: string | null
}

export type ContentSeoFilter = 'all' | 'pages' | 'posts'

export async function fetchSeoContentRecords(): Promise<SeoContentRecord[]> {
  const res = await cmsApi<{ records: SeoContentRecord[] }>('/seo/content')
  throwIfError(res)
  return res.data?.records ?? []
}

export async function fetchSeoContentRecord(
  entityType: string,
  entityId: string,
): Promise<SeoContentRecord | null> {
  const res = await cmsApi<SeoContentRecord>(
    `/seo/content/${entityType}/${encodeURIComponent(entityId)}`,
  )
  throwIfError(res)
  return res.data ?? null
}

export interface SeoFieldPayload {
  metaTitle?: string
  metaDescription?: string
  canonicalUrl?: string
  focusKeyword?: string
  ogTitle?: string
  ogDescription?: string
  ogImage?: string
  twitterTitle?: string
  twitterDescription?: string
  twitterImage?: string
  noindex?: boolean
  nofollow?: boolean
  structuredDataType?: string
  structuredData?: Record<string, unknown>
}

export async function updateSeoFields(
  entityType: string,
  entityId: string,
  payload: SeoFieldPayload,
): Promise<{ error?: string }> {
  const res = await cmsApi(`/seo/content/${entityType}/${encodeURIComponent(entityId)}`, {
    method: 'PUT',
    body: JSON.stringify(payload),
  })
  return res.error ? { error: res.error } : {}
}

/** Pure search/filter over the content records (decoupled from network for tests). */
export function applyContentSeoQuery(
  records: SeoContentRecord[],
  query: { search?: string; filter?: ContentSeoFilter },
): SeoContentRecord[] {
  const search = (query.search ?? '').trim().toLowerCase()
  const filter = query.filter ?? 'all'
  return records.filter((r) => {
    if (filter === 'pages' && r.entityType !== 'page') return false
    if (filter === 'posts' && r.entityType !== 'post') return false
    if (search) {
      const haystack = [
        r.title,
        r.url,
        r.metaTitle ?? '',
        r.metaDescription ?? '',
        String(r.seoScore),
        r.entityType,
      ]
        .join(' ')
        .toLowerCase()
      if (!haystack.includes(search)) return false
    }
    return true
  })
}

// ─── Audit ───────────────────────────────────────────────────────────

export type SeoAuditScope = 'full' | 'content' | 'technical' | 'redirect'

export interface SeoAuditRun {
  id: string
  status: 'RUNNING' | 'COMPLETED' | 'FAILED'
  scope: string
  startedAt: string
  finishedAt: string | null
  siteScoreBefore: number | null
  siteScoreAfter: number | null
  pagesScanned: number
  issuesFound: number
  criticalCount: number
  warningCount: number
  infoCount: number
  errorMessage: string | null
}

export async function runSeoAudit(
  scope: SeoAuditScope = 'full',
): Promise<{ run?: SeoAuditRun; error?: string }> {
  const res = await cmsApi<SeoAuditRun>('/seo/audit', {
    method: 'POST',
    body: JSON.stringify({ scope }),
  })
  if (res.error) return { error: res.error }
  return { run: res.data }
}

export async function fetchSeoAuditRuns(): Promise<SeoAuditRun[]> {
  const res = await cmsApi<{ runs: SeoAuditRun[] }>('/seo/audits')
  throwIfError(res)
  return res.data?.runs ?? []
}

export async function fetchSeoAuditRun(auditRunId: string): Promise<SeoAuditRun | null> {
  const res = await cmsApi<SeoAuditRun>(`/seo/audits/${encodeURIComponent(auditRunId)}`)
  throwIfError(res)
  return res.data ?? null
}

export interface SeoIssueQuery {
  severity?: SeoSeverity | 'all'
  category?: SeoIssueCategory | 'all'
  status?: SeoIssueStatus | 'all'
  auditRunId?: string
}

export async function fetchSeoIssues(query: SeoIssueQuery = {}): Promise<SeoIssue[]> {
  const params = new URLSearchParams()
  if (query.severity && query.severity !== 'all') params.set('severity', query.severity)
  if (query.category && query.category !== 'all') params.set('category', query.category)
  if (query.status && query.status !== 'all') params.set('status', query.status)
  if (query.auditRunId) params.set('auditRunId', query.auditRunId)
  const qs = params.toString()
  const res = await cmsApi<{ issues: SeoIssue[] }>(`/seo/issues${qs ? `?${qs}` : ''}`)
  throwIfError(res)
  return res.data?.issues ?? []
}

export async function fetchSeoIssue(issueId: string): Promise<SeoIssue | null> {
  const res = await cmsApi<SeoIssue>(`/seo/issues/${encodeURIComponent(issueId)}`)
  throwIfError(res)
  return res.data ?? null
}

export async function resolveSeoIssue(issueId: string): Promise<{ error?: string }> {
  const res = await cmsApi(`/seo/issues/${encodeURIComponent(issueId)}/resolve`, { method: 'POST' })
  return res.error ? { error: res.error } : {}
}

export async function ignoreSeoIssue(issueId: string): Promise<{ error?: string }> {
  const res = await cmsApi(`/seo/issues/${encodeURIComponent(issueId)}/ignore`, { method: 'POST' })
  return res.error ? { error: res.error } : {}
}

export async function reopenSeoIssue(issueId: string): Promise<{ error?: string }> {
  const res = await cmsApi(`/seo/issues/${encodeURIComponent(issueId)}/reopen`, { method: 'POST' })
  return res.error ? { error: res.error } : {}
}

// ─── Technical (sitemap / robots / crawl) ────────────────────────────

export interface SitemapStatus {
  sitemapUrl: string
  lastGeneratedAt: string | null
  pagesIncluded: number
  pagesExcluded: number
  autoRegenerateOnPublish: boolean
  errors: string[]
  warnings: string[]
}

export interface CrawlSettings {
  indexEntireSite: boolean
  includeImagesInSitemap: boolean
  includePostsInSitemap: boolean
  noindexPaginatedArchives: boolean
  noindexTagPages: boolean
}

export async function fetchSitemapStatus(): Promise<SitemapStatus | null> {
  const res = await cmsApi<SitemapStatus>('/seo/sitemap/status')
  throwIfError(res)
  return res.data ?? null
}

export async function regenerateSitemap(): Promise<{ status?: SitemapStatus; error?: string }> {
  const res = await cmsApi<SitemapStatus>('/seo/sitemap/regenerate', { method: 'POST' })
  if (res.error) return { error: res.error }
  return { status: res.data }
}

export async function fetchCrawlSettings(): Promise<CrawlSettings | null> {
  const res = await cmsApi<CrawlSettings>('/seo/crawl-settings')
  throwIfError(res)
  return res.data ?? null
}

export async function updateCrawlSettings(
  payload: Partial<CrawlSettings>,
): Promise<{ error?: string }> {
  const res = await cmsApi('/seo/crawl-settings', {
    method: 'PUT',
    body: JSON.stringify(payload),
  })
  return res.error ? { error: res.error } : {}
}

export interface RobotsConfig {
  body: string
  generated: boolean
}

export interface RobotsValidation {
  valid: boolean
  blocksEntireSite: boolean
  errors: string[]
  warnings: string[]
}

export async function fetchRobotsTxt(): Promise<RobotsConfig | null> {
  const res = await cmsApi<RobotsConfig>('/seo/robots')
  throwIfError(res)
  return res.data ?? null
}

export async function updateRobotsTxt(body: string): Promise<{ error?: string }> {
  const res = await cmsApi('/seo/robots', { method: 'PUT', body: JSON.stringify({ body }) })
  return res.error ? { error: res.error } : {}
}

/** Client-side robots.txt validation (mirrors server-side checks). */
export function validateRobotsTxt(body: string): RobotsValidation {
  const errors: string[] = []
  const warnings: string[] = []
  const lines = body.split(/\r?\n/)
  let blocksEntireSite = false
  let sawUserAgent = false
  let starAgent = false

  for (const raw of lines) {
    const line = raw.trim()
    if (!line || line.startsWith('#')) continue
    const colon = line.indexOf(':')
    if (colon === -1) {
      errors.push(`Invalid directive (missing ":"): "${line}"`)
      continue
    }
    const directive = line.slice(0, colon).trim().toLowerCase()
    const value = line.slice(colon + 1).trim()
    if (directive === 'user-agent') {
      sawUserAgent = true
      starAgent = value === '*'
    } else if (directive === 'disallow') {
      if (value === '/' && starAgent) blocksEntireSite = true
    } else if (!['allow', 'sitemap', 'crawl-delay', 'host'].includes(directive)) {
      warnings.push(`Unknown directive: "${directive}"`)
    }
  }

  if (!sawUserAgent) errors.push('No User-agent directive found.')
  if (blocksEntireSite) {
    warnings.push('This configuration blocks the entire site from all crawlers.')
  }
  return { valid: errors.length === 0, blocksEntireSite, errors, warnings }
}

// ─── Redirects ───────────────────────────────────────────────────────

export interface RedirectRule {
  id: string
  type: RedirectStatusCode
  fromPath: string
  toPath: string
  enabled: boolean
  priority: number
  preserveQuery: boolean
  hits: number
  lastHitAt: string | null
  stale: boolean
  source: string
  notes: string | null
  createdAt: string
  updatedAt: string
}

export interface RedirectOverview {
  activeRedirects: number
  staleRedirects: number
  notFoundCount: number
  notFoundHits: number
  chainsAndLoops: number
  trafficRecovered: number
}

export interface Redirect404 {
  id: string
  path: string
  hits: number
  lastSeenAt: string
}

export interface RedirectSuggestion {
  id: string
  fromPath: string
  toPath: string
  confidence: number
  reason: string | null
  status: 'open' | 'accepted' | 'dismissed'
  hits: number
  trend: string | null
}

export interface RedirectChain {
  id: string
  kind: 'chain' | 'loop'
  hops: string[]
  suggestedFrom: string
  suggestedTo: string
}

export async function fetchRedirectOverview(): Promise<RedirectOverview> {
  const res = await cmsApi<RedirectOverview>('/seo/redirects/overview')
  throwIfError(res)
  return (
    res.data ?? {
      activeRedirects: 0,
      staleRedirects: 0,
      notFoundCount: 0,
      notFoundHits: 0,
      chainsAndLoops: 0,
      trafficRecovered: 0,
    }
  )
}

export async function fetchRedirectRules(): Promise<RedirectRule[]> {
  const res = await cmsApi<{ rules: RedirectRule[] }>('/seo/redirects')
  throwIfError(res)
  return res.data?.rules ?? []
}

export interface RedirectRulePayload {
  type: RedirectStatusCode
  fromPath: string
  toPath: string
  enabled?: boolean
  preserveQuery?: boolean
  notes?: string
}

export async function createRedirectRule(
  payload: RedirectRulePayload,
): Promise<{ error?: string }> {
  const res = await cmsApi('/seo/redirects', { method: 'POST', body: JSON.stringify(payload) })
  return res.error ? { error: res.error } : {}
}

export async function updateRedirectRule(
  redirectId: string,
  payload: Partial<RedirectRulePayload>,
): Promise<{ error?: string }> {
  const res = await cmsApi(`/seo/redirects/${encodeURIComponent(redirectId)}`, {
    method: 'PUT',
    body: JSON.stringify(payload),
  })
  return res.error ? { error: res.error } : {}
}

export async function deleteRedirectRule(redirectId: string): Promise<{ error?: string }> {
  const res = await cmsApi(`/seo/redirects/${encodeURIComponent(redirectId)}`, {
    method: 'DELETE',
  })
  return res.error ? { error: res.error } : {}
}

export async function fetchRedirectChains(): Promise<RedirectChain[]> {
  const res = await cmsApi<{ chains: RedirectChain[] }>('/seo/redirects/chains')
  throwIfError(res)
  return res.data?.chains ?? []
}

export async function flattenRedirectChain(chainId: string): Promise<{ error?: string }> {
  const res = await cmsApi('/seo/redirects/flatten', {
    method: 'POST',
    body: JSON.stringify({ chainId }),
  })
  return res.error ? { error: res.error } : {}
}

export async function fetchRedirectSuggestions(): Promise<RedirectSuggestion[]> {
  const res = await cmsApi<{ suggestions: RedirectSuggestion[] }>('/seo/redirects/suggestions')
  throwIfError(res)
  return res.data?.suggestions ?? []
}

export async function generate404RecoverySuggestions(): Promise<{ error?: string }> {
  const res = await cmsApi('/seo/redirects/suggest', { method: 'POST' })
  return res.error ? { error: res.error } : {}
}

export async function acceptRedirectSuggestion(suggestionId: string): Promise<{ error?: string }> {
  const res = await cmsApi(
    `/seo/redirects/suggestions/${encodeURIComponent(suggestionId)}/accept`,
    {
      method: 'POST',
    },
  )
  return res.error ? { error: res.error } : {}
}

export async function dismissRedirectSuggestion(suggestionId: string): Promise<{ error?: string }> {
  const res = await cmsApi(
    `/seo/redirects/suggestions/${encodeURIComponent(suggestionId)}/dismiss`,
    { method: 'POST' },
  )
  return res.error ? { error: res.error } : {}
}

export async function exportRedirectsCsv(): Promise<{ csv?: string; error?: string }> {
  const res = await cmsApi<{ csv: string }>('/seo/redirects/export')
  if (res.error) return { error: res.error }
  return { csv: res.data?.csv ?? '' }
}

export async function importRedirectsCsv(
  csv: string,
): Promise<{ imported?: number; errors?: string[]; error?: string }> {
  const res = await cmsApi<{ imported: number; errors: string[] }>('/seo/redirects/import', {
    method: 'POST',
    body: JSON.stringify({ csv }),
  })
  if (res.error) return { error: res.error }
  return { imported: res.data?.imported ?? 0, errors: res.data?.errors ?? [] }
}

// ─── AI Copilot ──────────────────────────────────────────────────────

export interface SeoAiResult {
  text?: string
  alternatives?: string[]
  structuredData?: Record<string, unknown>
  /** True when the AI provider is not configured (graceful fallback). */
  unavailable?: boolean
}

/**
 * AI SEO endpoints report unavailability with two statuses: 501 (plugin-ai not
 * installed) and 503 (installed but the provider isn't configured). Both map to
 * the same graceful "AI unavailable" UX.
 */
function isAiUnavailable(status?: number): boolean {
  return status === 501 || status === 503
}

export async function generateSeoField(
  field: 'metaTitle' | 'metaDescription' | 'ogTitle' | 'ogDescription' | 'focusKeyword',
  context: { entityType: string; entityId: string },
): Promise<SeoAiResult> {
  const res = await cmsApi<SeoAiResult>('/ai/seo/generate-field', {
    method: 'POST',
    body: JSON.stringify({ field, ...context }),
  })
  if (isAiUnavailable(res.status)) return { unavailable: true }
  return res.data ?? {}
}

export async function explainSeoIssue(issueId: string): Promise<SeoAiResult> {
  const res = await cmsApi<SeoAiResult>('/ai/seo/explain-issue', {
    method: 'POST',
    body: JSON.stringify({ issueId }),
  })
  if (isAiUnavailable(res.status)) return { unavailable: true }
  return res.data ?? {}
}

export async function generateStructuredData(context: {
  entityType: string
  entityId: string
  schemaType: string
}): Promise<SeoAiResult> {
  const res = await cmsApi<SeoAiResult>('/ai/seo/generate-schema', {
    method: 'POST',
    body: JSON.stringify(context),
  })
  if (isAiUnavailable(res.status)) return { unavailable: true }
  return res.data ?? {}
}

export async function summarizeSeoAudit(): Promise<SeoAiResult> {
  const res = await cmsApi<SeoAiResult>('/ai/seo/summarize', { method: 'POST' })
  if (isAiUnavailable(res.status)) return { unavailable: true }
  return res.data ?? {}
}
