import type { HttpContext } from '@adonisjs/core/http'
import { DateTime } from 'luxon'

import Post from '#models/post'
import { renderPost, slugify } from '#services/blog_renderer'
import { createPostValidator, updatePostValidator, PUBLISH_REQUIREMENTS } from '#validators/post'
import PostTransformer from '#transformers/post_transformer'

const SITE_URL = 'https://rubis.pro'

/**
 * AdminPostsController — CRUD complet pour l'admin du blog.
 *
 * Routes (sous /api/v1/admin/posts/*, auth + admin) :
 *   GET    /                 → liste tous (drafts + published)
 *   POST   /                 → crée un brouillon
 *   GET    /:id              → détail (par UUID, pas par slug)
 *   PATCH  /:id              → modifie (re-render contentHtml + recompte mots)
 *   POST   /:id/publish      → durcit la validation puis flip published
 *   POST   /:id/unpublish    → revient en draft
 *   DELETE /:id              → hard delete (V1, on n'a pas beaucoup d'articles)
 *
 * Convention : on retourne TOUJOURS le post complet sérialisé après mutation
 * pour que l'admin SPA puisse refresh sans re-fetch.
 */
export default class AdminPostsController {
  /** GET /api/v1/admin/posts */
  async index({ response }: HttpContext) {
    const posts = await Post.query().orderBy('updatedAt', 'desc').orderBy('createdAt', 'desc')
    return response.json({
      data: posts.map((p) => new PostTransformer(p).toObject()),
    })
  }

  /** GET /api/v1/admin/posts/:id */
  async show({ params, response }: HttpContext) {
    const post = await Post.find(params.id)
    if (!post) return response.status(404).json({ error: 'post_not_found' })
    return response.json({ data: new PostTransformer(post).toObject() })
  }

  /** POST /api/v1/admin/posts */
  async store({ request, response }: HttpContext) {
    const payload = await request.validateUsing(createPostValidator)

    // Slug unicité
    const existing = await Post.findBy('slug', payload.slug)
    if (existing) {
      return response.status(422).json({
        errors: [{ field: 'slug', message: 'Slug déjà pris.' }],
      })
    }

    const { contentHtml, wordCount, readingTimeMinutes } = renderPost(payload.contentMd)
    const post = await Post.create({
      slug: payload.slug,
      title: payload.title,
      excerpt: payload.excerpt,
      contentMd: payload.contentMd,
      contentHtml,
      wordCount,
      readingTimeMinutes,
      tags: payload.tags ?? [],
      authorName: payload.authorName ?? 'Arthur Barré',
      heroImageUrl: payload.heroImageUrl ?? null,
      heroImageAlt: payload.heroImageAlt ?? null,
      ogImageUrl: payload.ogImageUrl ?? null,
      canonicalUrl: payload.canonicalUrl ?? null,
      noindex: payload.noindex ?? false,
      status: 'draft',
      aiGenerated: false,
    })

    return response.status(201).json({ data: new PostTransformer(post).toObject() })
  }

  /** PATCH /api/v1/admin/posts/:id */
  async update({ params, request, response }: HttpContext) {
    const post = await Post.find(params.id)
    if (!post) return response.status(404).json({ error: 'post_not_found' })

    const payload = await request.validateUsing(updatePostValidator)

    // Slug unicité si changement
    if (payload.slug && payload.slug !== post.slug) {
      const conflicting = await Post.findBy('slug', payload.slug)
      if (conflicting && conflicting.id !== post.id) {
        return response.status(422).json({
          errors: [{ field: 'slug', message: 'Slug déjà pris.' }],
        })
      }
      post.slug = payload.slug
    }

    if (payload.title !== undefined) post.title = payload.title
    if (payload.excerpt !== undefined) post.excerpt = payload.excerpt
    if (payload.tags !== undefined) post.tags = payload.tags
    if (payload.authorName !== undefined) post.authorName = payload.authorName
    if (payload.heroImageUrl !== undefined) post.heroImageUrl = payload.heroImageUrl
    if (payload.heroImageAlt !== undefined) post.heroImageAlt = payload.heroImageAlt
    if (payload.ogImageUrl !== undefined) post.ogImageUrl = payload.ogImageUrl
    if (payload.canonicalUrl !== undefined) post.canonicalUrl = payload.canonicalUrl
    if (payload.noindex !== undefined) post.noindex = payload.noindex

    if (payload.contentMd !== undefined) {
      post.contentMd = payload.contentMd
      const { contentHtml, wordCount, readingTimeMinutes } = renderPost(payload.contentMd)
      post.contentHtml = contentHtml
      post.wordCount = wordCount
      post.readingTimeMinutes = readingTimeMinutes
    }

    await post.save()
    return response.json({ data: new PostTransformer(post).toObject() })
  }

  /**
   * POST /api/v1/admin/posts/:id/publish
   * Durcit la validation SEO ici (on raisonne sur l'objet déjà persisté
   * avec wordCount calculé).
   */
  async publish({ params, response }: HttpContext) {
    const post = await Post.find(params.id)
    if (!post) return response.status(404).json({ error: 'post_not_found' })

    const errors: Array<{ field: string; message: string }> = []

    if (post.title.length > PUBLISH_REQUIREMENTS.titleMaxChars) {
      errors.push({
        field: 'title',
        message: `Titre trop long pour Google (${post.title.length} > ${PUBLISH_REQUIREMENTS.titleMaxChars} chars).`,
      })
    }
    if (
      post.excerpt.length < PUBLISH_REQUIREMENTS.excerptMinChars
      || post.excerpt.length > PUBLISH_REQUIREMENTS.excerptMaxChars
    ) {
      errors.push({
        field: 'excerpt',
        message: `L'excerpt doit faire entre ${PUBLISH_REQUIREMENTS.excerptMinChars} et ${PUBLISH_REQUIREMENTS.excerptMaxChars} chars (actuel : ${post.excerpt.length}).`,
      })
    }
    if (!post.heroImageUrl) {
      errors.push({ field: 'heroImageUrl', message: 'Hero image requise pour publication.' })
    }
    if (!post.heroImageAlt) {
      errors.push({ field: 'heroImageAlt', message: "Texte alt de l'image hero requis (SEO + accessibilité)." })
    }
    if (post.wordCount < PUBLISH_REQUIREMENTS.minWordCount) {
      errors.push({
        field: 'contentMd',
        message: `L'article doit faire au moins ${PUBLISH_REQUIREMENTS.minWordCount} mots (actuel : ${post.wordCount}).`,
      })
    }

    if (errors.length > 0) {
      return response.status(422).json({ errors })
    }

    post.status = 'published'
    post.publishedAt = DateTime.now().toUTC().startOf('minute')
    await post.save()

    // Ping fire-and-forget — on n'attend pas (pas critique).
    pingSitemap(SITE_URL).catch(() => {
      /* swallow — l'article est publié, le ping est best-effort */
    })

    return response.json({ data: new PostTransformer(post).toObject() })
  }

  /** POST /api/v1/admin/posts/:id/unpublish */
  async unpublish({ params, response }: HttpContext) {
    const post = await Post.find(params.id)
    if (!post) return response.status(404).json({ error: 'post_not_found' })

    post.status = 'draft'
    // On garde publishedAt en historique (même si plus visible publiquement).
    await post.save()
    return response.json({ data: new PostTransformer(post).toObject() })
  }

  /** DELETE /api/v1/admin/posts/:id */
  async destroy({ params, response }: HttpContext) {
    const post = await Post.find(params.id)
    if (!post) return response.status(404).json({ error: 'post_not_found' })
    await post.delete()
    return response.status(204)
  }

  /**
   * GET /api/v1/admin/posts/suggest-slug?title=...
   * Helper pour l'UI : génère un slug propre depuis un titre.
   */
  async suggestSlug({ request, response }: HttpContext) {
    const title = String(request.input('title', '')).trim()
    if (!title) return response.json({ data: { slug: '' } })

    const base = slugify(title)
    // Si dispo, on retourne tel quel ; sinon on suffixe.
    let candidate = base
    let n = 2
    while (await Post.findBy('slug', candidate)) {
      candidate = `${base}-${n}`
      n += 1
    }
    return response.json({ data: { slug: candidate } })
  }
}

/**
 * Ping Google + Bing pour qu'ils re-crawlent le sitemap.
 * Ces endpoints sont stables et n'exigent pas d'auth pour les sitemaps publics.
 */
async function pingSitemap(siteUrl: string): Promise<void> {
  const sitemapUrl = encodeURIComponent(`${siteUrl}/sitemap.xml`)
  await Promise.allSettled([
    fetch(`https://www.google.com/ping?sitemap=${sitemapUrl}`),
    fetch(`https://www.bing.com/ping?sitemap=${sitemapUrl}`),
  ])
}
