const path = require('path')
const fs = require('fs')
const crypto = require('crypto')

const express = require('express')
const cors = require('cors')

const { upload } = require('./storage')
const { query, closePool } = require('./db')
const { runMigrations } = require('./migrations')

const PORT = Number(process.env.API_PORT) || 4005
const MAX_SECTIONS = Number(process.env.BLOG_MAX_SECTIONS || 5)

const app = express()

const allowedOrigins = (process.env.CORS_ORIGINS || 'http://localhost:3000')
  .split(',')
  .map(origin => origin.trim())
  .filter(Boolean)

app.use(cors({
  origin: allowedOrigins,
  credentials: true
}))

app.use(express.json({ limit: '2mb' }))
app.use(express.urlencoded({ extended: true }))

const uploadsPath = path.join(__dirname, '..', 'public', 'uploads')
if (!fs.existsSync(uploadsPath)) {
  fs.mkdirSync(uploadsPath, { recursive: true })
}
app.use('/uploads', express.static(uploadsPath))

app.get('/health', (_req, res) => {
  res.json({ status: 'ok' })
})

function slugify(value) {
  return value
    .toString()
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)+/g, '') || crypto.randomUUID()
}

function normalizeCategoryForSearch(category) {
  if (!category) return null
  // Convert slug format to display format for backward compatibility
  const categoryMap = {
    'books-magazine': 'Books & Magazine',
    'clothing-shoes': 'Clothing & Shoes',
    'collectibles-art': 'Collectibles & Art',
    'toys-hobbies': 'Toys & Hobbies'
  }
  return categoryMap[category.toLowerCase()] || category
}

async function generateUniqueSlug(title, excludeId) {
  const base = slugify(title)
  let slug = base
  let suffix = 1

  // eslint-disable-next-line no-constant-condition
  while (true) {
    const values = [slug]
    let condition = ''

    const excludeNumeric = Number(excludeId)
    if (Number.isInteger(excludeNumeric) && excludeNumeric > 0) {
      condition = 'AND id <> $2'
      values.push(excludeNumeric)
    }

    const existing = await query(
      `SELECT id FROM blog_posts WHERE slug = $1 ${condition} LIMIT 1`,
      values
    )

    if (existing.rows.length === 0) {
      return slug
    }

    suffix += 1
    slug = `${base}-${suffix}`
  }
}

function parsePayload(body) {
  if (body.payload) {
    try {
      return JSON.parse(body.payload)
    } catch (error) {
      throw new Error('Invalid payload JSON')
    }
  }
  return body
}

function buildMainImage(payload, files) {
  const uploadForMain = files.mainImage && files.mainImage[0]
  if (payload.removeMainImage === true || payload.removeMainImage === 'true') {
    return null
  }
  if (uploadForMain) {
    return `/uploads/${uploadForMain.filename}`
  }
  if (payload.existingMainImage) {
    return payload.existingMainImage
  }
  return null
}

function buildSections(payload, files) {
  const sections = []
  const inputSections = Array.isArray(payload.sections) ? payload.sections : []

  for (let index = 0; index < MAX_SECTIONS; index += 1) {
    const sectionInput = inputSections[index] || {}
    const fileKey = `section${index}Image`
    const uploadForSection = files[fileKey] && files[fileKey][0]

    const rawText = typeof sectionInput.text === 'string' ? sectionInput.text : ''
    const text = rawText.trim()
    const image = uploadForSection
      ? `/uploads/${uploadForSection.filename}`
      : sectionInput.existingImage || null

    if (text || image) {
      sections.push({
        id: sectionInput.id || crypto.randomUUID(),
        text: text || null,
        image
      })
    }
  }

  return sections
}

function mapPostRow(row) {
  return {
    id: row.id,
    title: row.title,
    slug: row.slug,
    previewImage: row.preview_image,
    linkUrl: row.link_url,
    sections: row.sections || [],
    footer: row.footer,
    isEditorsPick: row.is_editors_pick,
    category: row.category,
    createdAt: row.created_at,
    updatedAt: row.updated_at
  }
}

function createExcerpt(sections) {
  const firstText = sections
    .map(section => section.text || '')
    .find(text => text && text.trim().length > 0)

  if (!firstText) {
    return ''
  }

  const trimmed = firstText.trim()
  if (trimmed.length <= 220) {
    return trimmed
  }

  return `${trimmed.slice(0, 217)}...`
}

async function ensureEditorsPickLimit(targetId, makePick) {
  if (!makePick) {
    return
  }

  let condition = ''
  const params = []

  if (Number.isInteger(targetId) && targetId > 0) {
    condition = 'AND id <> $1'
    params.push(targetId)
  }

  const result = await query(
    `SELECT id FROM blog_posts WHERE is_editors_pick = true ${condition}`,
    params
  )

  if (result.rows.length >= 3) {
    const ids = result.rows.map(r => r.id)
    throw new Error(`Only three editor's picks allowed. Currently set: ${ids.join(', ')}`)
  }
}

function getUploadFields() {
  const fields = [{ name: 'mainImage', maxCount: 1 }]
  for (let index = 0; index < MAX_SECTIONS; index += 1) {
    fields.push({ name: `section${index}Image`, maxCount: 1 })
  }
  return fields
}

app.get('/posts', async (req, res) => {
  try {
    const { category } = req.query
    let queryText = 'SELECT * FROM blog_posts'
    const queryParams = []

    if (category) {
      const normalizedCategory = normalizeCategoryForSearch(category)
      queryText += ' WHERE category = $1'
      queryParams.push(normalizedCategory)
    }

    queryText += ' ORDER BY created_at DESC'

    const result = await query(queryText, queryParams)
    const posts = result.rows.map(mapPostRow).map(post => ({
      ...post,
      excerpt: createExcerpt(post.sections)
    }))
    res.json({ data: posts })
  } catch (error) {
    console.error('[GET /posts] error', error)
    res.status(500).json({ error: 'Failed to fetch posts' })
  }
})

app.get('/posts/:id', async (req, res) => {
  const { id } = req.params
  try {
    // Try to parse as integer ID first, otherwise treat as slug
    const numericId = parseInt(id, 10)
    let result

    if (!isNaN(numericId) && numericId.toString() === id) {
      // It's a numeric ID
      result = await query('SELECT * FROM blog_posts WHERE id = $1', [numericId])
    } else {
      // It's a slug
      result = await query('SELECT * FROM blog_posts WHERE slug = $1', [id])
    }

    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'Post not found' })
    }
    const post = mapPostRow(result.rows[0])
    post.excerpt = createExcerpt(post.sections)
    return res.json({ data: post })
  } catch (error) {
    console.error('[GET /posts/:id] error', error)
    return res.status(500).json({ error: 'Failed to fetch post' })
  }
})

app.post('/posts', upload.fields(getUploadFields()), async (req, res) => {
  try {
    const payload = parsePayload(req.body)

    if (!payload.title || !payload.title.trim()) {
      return res.status(400).json({ error: 'Title is required' })
    }

    const mainImage = buildMainImage(payload, req.files)
    const sections = buildSections(payload, req.files)
    const slug = await generateUniqueSlug(payload.title.trim())
    const isEditorsPick = Boolean(payload.isEditorsPick)

    await ensureEditorsPickLimit(null, isEditorsPick)

    const result = await query(
      `INSERT INTO blog_posts (title, slug, preview_image, link_url, sections, footer, is_editors_pick, category)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [
        payload.title.trim(),
        slug,
        mainImage,
        payload.linkUrl || null,
        JSON.stringify(sections),
        payload.footer || null,
        isEditorsPick,
        payload.category || null
      ]
    )

    if (isEditorsPick) {
      try {
        await ensureEditorsPickLimit(result.rows[0].id, true)
      } catch (limitError) {
        await query('UPDATE blog_posts SET is_editors_pick = false WHERE id = $1', [result.rows[0].id])
        throw limitError
      }
    }

    const post = mapPostRow(result.rows[0])
    post.excerpt = createExcerpt(post.sections)
    res.status(201).json({ data: post })
  } catch (error) {
    console.error('[POST /posts] error', error)
    const message = error.message || 'Failed to create post'
    res.status(400).json({ error: message })
  }
})

app.put('/posts/:id', upload.fields(getUploadFields()), async (req, res) => {
  const { id } = req.params
  try {
    const payload = parsePayload(req.body)

    if (!payload.title || !payload.title.trim()) {
      return res.status(400).json({ error: 'Title is required' })
    }

    const existingResult = await query('SELECT * FROM blog_posts WHERE id = $1', [id])
    if (existingResult.rows.length === 0) {
      return res.status(404).json({ error: 'Post not found' })
    }

    const existingPost = mapPostRow(existingResult.rows[0])

    const mainImage = buildMainImage(payload, req.files)
    const sections = buildSections(payload, req.files)
    const nextSlug = await generateUniqueSlug(payload.title.trim(), Number(id))
    const isEditorsPick = Boolean(payload.isEditorsPick)

    if (isEditorsPick && !existingPost.isEditorsPick) {
      await ensureEditorsPickLimit(Number(id), true)
    }

    const result = await query(
      `UPDATE blog_posts
       SET title = $1,
           slug = $2,
           preview_image = $3,
           link_url = $4,
           sections = $5,
           footer = $6,
           is_editors_pick = $7,
           category = $8
       WHERE id = $9
       RETURNING *`,
      [
        payload.title.trim(),
        nextSlug,
        mainImage,
        payload.linkUrl || null,
        JSON.stringify(sections),
        payload.footer || null,
        isEditorsPick,
        payload.category || null,
        id
      ]
    )

    const post = mapPostRow(result.rows[0])
    post.excerpt = createExcerpt(post.sections)
    res.json({ data: post })
  } catch (error) {
    console.error('[PUT /posts/:id] error', error)
    const status = error.message && error.message.includes('editor') ? 400 : 500
    res.status(status).json({ error: error.message || 'Failed to update post' })
  }
})

app.delete('/posts/:id', async (req, res) => {
  const { id } = req.params
  try {
    const existing = await query('SELECT * FROM blog_posts WHERE id = $1', [id])
    if (existing.rows.length === 0) {
      return res.status(404).json({ error: 'Post not found' })
    }

    await query('DELETE FROM blog_posts WHERE id = $1', [id])
    return res.json({ success: true })
  } catch (error) {
    console.error('[DELETE /posts/:id] error', error)
    return res.status(500).json({ error: 'Failed to delete post' })
  }
})

app.patch('/posts/:id/editors-pick', async (req, res) => {
  const { id } = req.params
  const makePick = Boolean(req.body?.isEditorsPick)

  try {
    const existing = await query('SELECT * FROM blog_posts WHERE id = $1', [id])
    if (existing.rows.length === 0) {
      return res.status(404).json({ error: 'Post not found' })
    }

    if (makePick && !existing.rows[0].is_editors_pick) {
      await ensureEditorsPickLimit(Number(id), true)
    }

    const result = await query(
      'UPDATE blog_posts SET is_editors_pick = $1 WHERE id = $2 RETURNING *',
      [makePick, id]
    )

    const post = mapPostRow(result.rows[0])
    post.excerpt = createExcerpt(post.sections)
    return res.json({ data: post })
  } catch (error) {
    console.error('[PATCH /posts/:id/editors-pick] error', error)
    const status = error.message && error.message.includes('Only three') ? 400 : 500
    return res.status(status).json({ error: error.message || 'Failed to update editor pick' })
  }
})

async function start() {
  try {
    await runMigrations()
    app.listen(PORT, '0.0.0.0', () => {
      console.log(`[api] listening on port ${PORT}`)
    })
  } catch (error) {
    console.error('[api] failed to start', error)
    await closePool()
    process.exit(1)
  }
}

start()




