const posts = require('../../../model/posts/posts.model')
const wiki = require('../../../model/wiki/wiki.model')
const settings = require('../../../model/settings/settings.model')
const users = require('../../../model/users/users.model')
const activity = require('../../../model/activity/activity.model')
const botInternalClient = require('../../../utils/botInternalClient')

const log = require('../../../utils/logger')('admin')

// Public base URL for links back to the site — same fallback pattern as
// sso.controller.js's redirect_uri builder.
function appBaseUrl(req) {
  const configured = process.env.APP_BASE_URL
  if (configured) return configured.replace(/\/+$/, '')
  return `${req.protocol}://${req.get('host')}`
}

// Fire-and-forget: announce a news post to Discord the moment it actually
// transitions from unpublished to published — not on every save or on a
// no-op re-publish of an already-live post. Never throws (botInternalClient
// itself never rejects); a bot outage must never break publishing a post.
function announceIfNewlyPublished(req, post, wasPublished) {
  if (!post || post.category !== 'news' || !post.published || wasPublished) return
  const base = appBaseUrl(req)
  // image_url is stored relative (e.g. "/uploads/xyz.png") — Discord embeds
  // require an absolute URL.
  const imageUrl = post.image_url ? new URL(post.image_url, base).toString() : null
  botInternalClient
    .announce({
      title: post.title,
      excerpt: post.excerpt,
      url: `${base}/site/news`,
      imageUrl,
    })
    .catch(() => {})
}

// ── Dashboard & site mode ─────────────────────────────────────────────
async function dashboard(req, res) {
  try {
    return res.json({
      site_mode: (await settings.get('site_mode')) || 'live',
      last_change: {
        at: await settings.get('site_mode_changed_at'),
        by: await settings.get('site_mode_changed_by'),
      },
      counts: {
        posts: await posts.counts(),
        users: await users.count(),
      },
      recent_activity: await activity.list({ limit: 10 }),
    })
  } catch (err) {
    log.error('dashboard', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function setSiteMode(req, res) {
  const { mode } = req.body
  try {
    const changedAt = new Date().toISOString()
    await settings.setMany(
      {
        site_mode: mode,
        site_mode_changed_at: changedAt,
        site_mode_changed_by: req.user.username,
      },
      req.user.id,
    )
    await activity.log({ req, action: 'site_mode.change', detail: { mode } })
    log.info('site mode changed', { mode, by: req.user.username, ip: req.ip })
    return res.json({ site_mode: mode, changed_at: changedAt, changed_by: req.user.username })
  } catch (err) {
    log.error('setSiteMode', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Posts ─────────────────────────────────────────────────────────────
async function listPosts(req, res) {
  try {
    let dbCategory = null
    if (req.query.category) {
      dbCategory = posts.toDbCategory(req.query.category)
      if (!dbCategory) return res.status(400).json({ message: 'Unknown category' })
    }
    return res.json(await posts.listAll(dbCategory))
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getPost(req, res) {
  try {
    const post = await posts.getById(Number(req.params.id))
    if (!post) return res.status(404).json({ message: 'Not found' })
    return res.json(post)
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function createPost(req, res) {
  const dbCategory = posts.toDbCategory(req.body.category)
  if (!dbCategory) return res.status(400).json({ message: 'Unknown category' })
  if (dbCategory === 'screenshot' && !req.body.image_url) {
    return res.status(400).json({ message: 'Screenshots require an image_url' })
  }
  try {
    const created = await posts.create({
      category: dbCategory,
      title: req.body.title,
      slug: req.body.slug || null,
      excerpt: req.body.excerpt || null,
      body: req.body.body || null,
      image_url: req.body.image_url || null,
      published: Boolean(req.body.published),
      author_id: req.user.id,
    })
    await activity.log({ req, action: 'post.create', detail: { id: created.id, category: dbCategory } })
    announceIfNewlyPublished(req, created, false)
    return res.status(201).json(created)
  } catch (err) {
    log.error('createPost', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function updatePost(req, res) {
  const id = Number(req.params.id)
  try {
    const current = await posts.getById(id)
    if (!current) return res.status(404).json({ message: 'Not found' })

    const fields = {}
    for (const key of ['title', 'slug', 'excerpt', 'body', 'image_url']) {
      if (key in req.body) fields[key] = req.body[key] || null
    }
    if ('category' in req.body) {
      const dbCategory = posts.toDbCategory(req.body.category)
      if (!dbCategory) return res.status(400).json({ message: 'Unknown category' })
      fields.category = dbCategory
    }
    if ('published' in req.body) {
      fields.published = req.body.published ? 1 : 0
      if (req.body.published && !current.published_at) fields.published_at = new Date()
    }

    const updated = await posts.update(id, fields)
    await activity.log({ req, action: 'post.update', detail: { id } })
    announceIfNewlyPublished(req, updated, Boolean(current.published))
    return res.json(updated)
  } catch (err) {
    log.error('updatePost', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function publishPost(req, res) {
  const id = Number(req.params.id)
  try {
    const current = await posts.getById(id)
    if (!current) return res.status(404).json({ message: 'Not found' })
    const updated = await posts.setPublished(id, Boolean(req.body.published))
    await activity.log({
      req,
      action: 'post.publish',
      detail: { id, published: Boolean(req.body.published) },
    })
    announceIfNewlyPublished(req, updated, Boolean(current.published))
    return res.json(updated)
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function deletePost(req, res) {
  const id = Number(req.params.id)
  try {
    await posts.remove(id)
    await activity.log({ req, action: 'post.delete', detail: { id } })
    return res.json({ id })
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function uploadImage(req, res) {
  if (!req.file) return res.status(400).json({ message: 'No image uploaded' })
  const imageUrl = `/uploads/${req.file.filename}`
  await activity.log({ req, action: 'post.upload', detail: { image_url: imageUrl } })
  return res.status(201).json({ image_url: imageUrl })
}

// Generalized upload used by rich-text editors (wiki, etc.). Same multer config
// as the screenshot upload; returns a neutral { url }.
async function uploadFile(req, res) {
  if (!req.file) return res.status(400).json({ message: 'No file uploaded' })
  const url = `/uploads/${req.file.filename}`
  await activity.log({ req, action: 'upload', detail: { url } })
  return res.status(201).json({ url })
}

// ── Wiki pages ─────────────────────────────────────────────────────────
async function listWiki(req, res) {
  try {
    const q = (req.query.q || '').trim()
    if (q) return res.json(await wiki.search(q, { publishedOnly: false }))

    const filters = {}
    if (req.query.category) {
      const category = await wiki.getCategoryBySlug(req.query.category)
      filters.categoryId = category ? category.id : -1 // unknown → match nothing
    }
    if (req.query.tag) {
      const tag = await wiki.getTagBySlug(req.query.tag)
      filters.tagId = tag ? tag.id : -1 // unknown → match nothing
    }
    if (req.query.status === 'draft') filters.published = false
    if (req.query.status === 'published') filters.published = true
    return res.json(await wiki.listAll(filters))
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getWiki(req, res) {
  try {
    const page = await wiki.getBySlug(req.params.slug)
    if (!page) return res.status(404).json({ message: 'Not found' })
    return res.json(page)
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// Resolve a category_id from the request, validating it exists. Returns
// { ok, value } so the caller can distinguish "not provided" from "invalid".
async function resolveCategoryId(body) {
  if (!('category_id' in body) || body.category_id == null || body.category_id === '') {
    return { ok: true, value: null }
  }
  const category = await wiki.getCategoryById(Number(body.category_id))
  if (!category) return { ok: false }
  return { ok: true, value: category.id }
}

async function createWiki(req, res) {
  try {
    if (await wiki.getBySlug(req.body.slug)) {
      return res.status(409).json({ message: 'A page with that slug already exists' })
    }
    const cat = await resolveCategoryId(req.body)
    if (!cat.ok) return res.status(400).json({ message: 'Unknown category' })

    const page = await wiki.create({
      slug: req.body.slug,
      title: req.body.title,
      body: req.body.body || null,
      excerpt: req.body.excerpt || null,
      categoryId: cat.value,
      published: req.body.published !== false,
      tags: Array.isArray(req.body.tags) ? req.body.tags : undefined,
      updatedBy: req.user.id,
    })
    await activity.log({ req, action: 'wiki.create', detail: { slug: page.slug } })
    return res.status(201).json(page)
  } catch (err) {
    log.error('createWiki', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function updateWiki(req, res) {
  try {
    const existing = await wiki.getBySlug(req.params.slug)
    if (!existing) return res.status(404).json({ message: 'Not found' })

    const input = { updatedBy: req.user.id }
    if ('title' in req.body) input.title = req.body.title
    if ('body' in req.body) input.body = req.body.body || null
    if ('excerpt' in req.body) input.excerpt = req.body.excerpt || null
    if ('published' in req.body) input.published = Boolean(req.body.published)
    if ('change_note' in req.body) input.changeNote = req.body.change_note
    if ('tags' in req.body) input.tags = Array.isArray(req.body.tags) ? req.body.tags : []
    if ('category_id' in req.body) {
      const cat = await resolveCategoryId(req.body)
      if (!cat.ok) return res.status(400).json({ message: 'Unknown category' })
      input.categoryId = cat.value
    }

    const page = await wiki.update(req.params.slug, input)
    await activity.log({ req, action: 'wiki.update', detail: { slug: req.params.slug } })
    return res.json(page)
  } catch (err) {
    log.error('updateWiki', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function publishWiki(req, res) {
  try {
    const page = await wiki.setPublished(req.params.slug, Boolean(req.body.published))
    if (!page) return res.status(404).json({ message: 'Not found' })
    await activity.log({
      req,
      action: 'wiki.publish',
      detail: { slug: req.params.slug, published: Boolean(req.body.published) },
    })
    return res.json(page)
  } catch (err) {
    log.error('publishWiki', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function deleteWiki(req, res) {
  try {
    await wiki.remove(req.params.slug)
    await activity.log({ req, action: 'wiki.delete', detail: { slug: req.params.slug } })
    return res.json({ slug: req.params.slug })
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Wiki revisions ─────────────────────────────────────────────────────
async function listWikiRevisions(req, res) {
  try {
    const revisions = await wiki.listRevisions(req.params.slug)
    if (revisions == null) return res.status(404).json({ message: 'Not found' })
    return res.json(revisions)
  } catch (err) {
    log.error('listWikiRevisions', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function getWikiRevision(req, res) {
  try {
    const rev = await wiki.getRevision(req.params.slug, Number(req.params.id))
    if (!rev) return res.status(404).json({ message: 'Not found' })
    return res.json(rev)
  } catch (err) {
    log.error('getWikiRevision', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function restoreWikiRevision(req, res) {
  try {
    const page = await wiki.restoreRevision(req.params.slug, Number(req.params.id), req.user.id)
    if (!page) return res.status(404).json({ message: 'Not found' })
    await activity.log({
      req,
      action: 'wiki.revision.restore',
      detail: { slug: req.params.slug, revision: Number(req.params.id) },
    })
    return res.json(page)
  } catch (err) {
    log.error('restoreWikiRevision', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Wiki tags ──────────────────────────────────────────────────────────
async function listWikiTags(req, res) {
  try {
    return res.json(await wiki.listTags())
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Wiki categories ────────────────────────────────────────────────────
async function listWikiCategories(req, res) {
  try {
    return res.json(await wiki.listCategories())
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function createWikiCategory(req, res) {
  try {
    if (await wiki.getCategoryBySlug(req.body.slug)) {
      return res.status(409).json({ message: 'A category with that slug already exists' })
    }
    const category = await wiki.createCategory({
      slug: req.body.slug,
      title: req.body.title,
      description: req.body.description || null,
      sortOrder: Number(req.body.sort_order) || 0,
    })
    await activity.log({ req, action: 'wiki.category.create', detail: { slug: category.slug } })
    return res.status(201).json(category)
  } catch (err) {
    log.error('createWikiCategory', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function updateWikiCategory(req, res) {
  const id = Number(req.params.id)
  try {
    const existing = await wiki.getCategoryById(id)
    if (!existing) return res.status(404).json({ message: 'Not found' })

    const input = {}
    if ('title' in req.body) input.title = req.body.title
    if ('description' in req.body) input.description = req.body.description || null
    if ('sort_order' in req.body) input.sortOrder = Number(req.body.sort_order) || 0
    if ('slug' in req.body && req.body.slug !== existing.slug) {
      const clash = await wiki.getCategoryBySlug(req.body.slug)
      if (clash) return res.status(409).json({ message: 'A category with that slug already exists' })
      input.slug = req.body.slug
    }

    const category = await wiki.updateCategory(id, input)
    await activity.log({ req, action: 'wiki.category.update', detail: { id } })
    return res.json(category)
  } catch (err) {
    log.error('updateWikiCategory', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function deleteWikiCategory(req, res) {
  const id = Number(req.params.id)
  try {
    const existing = await wiki.getCategoryById(id)
    if (!existing) return res.status(404).json({ message: 'Not found' })
    await wiki.removeCategory(id) // pages in it become uncategorized
    await activity.log({ req, action: 'wiki.category.delete', detail: { id } })
    return res.json({ id })
  } catch (err) {
    log.error('deleteWikiCategory', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Settings ──────────────────────────────────────────────────────────
async function getSettings(req, res) {
  try {
    return res.json(await settings.getAll())
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function updateSettings(req, res) {
  const updates = req.body
  if (!updates || typeof updates !== 'object' || Array.isArray(updates)) {
    return res.status(400).json({ message: 'Expected an object of key/value settings' })
  }
  try {
    await settings.setMany(updates, req.user.id)
    await activity.log({ req, action: 'settings.update', detail: { keys: Object.keys(updates) } })
    return res.json(await settings.getAll())
  } catch (err) {
    log.error('updateSettings', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── Activity log ──────────────────────────────────────────────────────
async function listActivity(req, res) {
  const limit = Math.min(Number(req.query.limit) || 50, 200)
  const offset = Number(req.query.offset) || 0
  try {
    return res.json(await activity.list({ limit, offset }))
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// ── User management ───────────────────────────────────────────────────
async function listUsers(req, res) {
  try {
    return res.json(await users.list())
  } catch (err) {
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function createUser(req, res) {
  try {
    if (await users.getRawByUsername(req.body.username)) {
      return res.status(409).json({ message: 'Username already taken' })
    }
    const user = await users.createUser({
      username: req.body.username,
      password: req.body.password,
      role: req.body.role || 'admin',
    })
    await activity.log({ req, action: 'user.create', detail: { id: user.id, username: user.username } })
    return res.status(201).json(user)
  } catch (err) {
    log.error('createUser', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function updateUser(req, res) {
  const id = Number(req.params.id)
  try {
    const target = await users.getById(id)
    if (!target) return res.status(404).json({ message: 'Not found' })

    // If the username is changing, make sure no other user already has it —
    // return 409 rather than letting the DB unique constraint throw a 500.
    if (req.body.username && req.body.username !== target.username) {
      const clash = await users.getRawByUsername(req.body.username)
      if (clash) return res.status(409).json({ message: 'Username already taken' })
    }

    // Don't let the last admin demote themselves out of admin access.
    if (target.role === 'admin' && req.body.role && req.body.role !== 'admin') {
      if ((await users.countAdmins()) <= 1) {
        return res.status(400).json({ message: 'Cannot demote the last admin' })
      }
    }
    const user = await users.update(id, {
      username: req.body.username,
      password: req.body.password,
      role: req.body.role,
    })
    await activity.log({ req, action: 'user.update', detail: { id } })
    return res.json(user)
  } catch (err) {
    log.error('updateUser', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

async function deleteUser(req, res) {
  const id = Number(req.params.id)
  try {
    if (id === req.user.id) {
      return res.status(400).json({ message: 'You cannot delete your own account' })
    }
    const target = await users.getById(id)
    if (!target) return res.status(404).json({ message: 'Not found' })
    if (target.role === 'admin' && (await users.countAdmins()) <= 1) {
      return res.status(400).json({ message: 'Cannot delete the last admin' })
    }
    await users.remove(id)
    await activity.log({ req, action: 'user.delete', detail: { id } })
    return res.json({ id })
  } catch (err) {
    log.error('deleteUser', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

module.exports = {
  dashboard,
  setSiteMode,
  listPosts,
  getPost,
  createPost,
  updatePost,
  publishPost,
  deletePost,
  uploadImage,
  uploadFile,
  listWiki,
  getWiki,
  createWiki,
  updateWiki,
  publishWiki,
  deleteWiki,
  listWikiRevisions,
  getWikiRevision,
  restoreWikiRevision,
  listWikiTags,
  listWikiCategories,
  createWikiCategory,
  updateWikiCategory,
  deleteWikiCategory,
  getSettings,
  updateSettings,
  listActivity,
  listUsers,
  createUser,
  updateUser,
  deleteUser,
}
