import { Hono } from 'hono';
import type { Context } from 'hono';
import { z } from 'zod';
import type { Env } from '../env';
import { adminApiOnly, adminOnly, shouldBootstrap } from '../middleware/admin';
import { createSession, registerUser } from '../services/auth';
import { getSettings, setSettings, SETTING_KEYS } from '../services/settings';
import { createLink, deleteLink, listAdminLinks, updateLink } from '../services/links';
import { all, audit } from '../db/queries';
import { nowSeconds } from '../utils/time';
import { apiOk, setSessionCookie } from './helpers';
import { AdminBootstrapPage } from '../views/admin/bootstrap';
import { AdminDashboard } from '../views/admin/dashboard';
import { AdminUsersPage } from '../views/admin/users';
import { AdminPostsPage } from '../views/admin/posts';
import { AdminCommentsPage } from '../views/admin/comments';
import { AdminLinksPage } from '../views/admin/links';
import { AdminSettingsPage } from '../views/admin/settings';
import { AdminAuditPage } from '../views/admin/audit';

export const admin = new Hono<Env>();

async function parseJson<T>(c: Context<Env>, schema: z.ZodType<T>): Promise<T> {
  const json = await c.req.json().catch(() => null);
  const parsed = schema.safeParse(json);
  if (!parsed.success) {
    const error = new Error(parsed.error.issues[0]?.message || '请求参数无效') as Error & { status: number; code: string };
    error.status = 422;
    error.code = 'VALIDATION_ERROR';
    throw error;
  }
  return parsed.data;
}

function clientIp(c: Context<Env>): string {
  return c.req.header('cf-connecting-ip') || c.req.header('x-forwarded-for') || 'local';
}

admin.get('/bootstrap', async (c) => {
  if (!(await shouldBootstrap(c.env))) return c.redirect('/admin');
  const settings = await getSettings(c.env);
  return c.html(<AdminBootstrapPage settings={settings} csrfToken={c.get('csrfToken')} />);
});

admin.post('/api/bootstrap', async (c) => {
  if (!(await shouldBootstrap(c.env))) return apiOk(c, { disabled: true });
  const input = await parseJson(
    c,
    z.object({
      username: z.string().regex(/^[a-zA-Z0-9_]{3,20}$/),
      email: z.string().email(),
      password: z.string().min(8),
      displayName: z.string().min(1),
      bootstrapToken: z.string().min(1)
    })
  );
  const user = await registerUser(c.env, input);
  const session = await createSession(c.env, user.id, c.req.raw);
  setSessionCookie(c, session.token, session.maxAge);
  await audit(c.env.DB, { actorId: user.id, action: 'bootstrap.createAdmin', target: user.id, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { user });
});

admin.use('*', async (c, next) => {
  if (c.req.path.endsWith('/bootstrap') || c.req.path.endsWith('/api/bootstrap')) {
    await next();
    return;
  }
  if (await shouldBootstrap(c.env)) return c.redirect('/admin/bootstrap');
  return adminOnly(c, next);
});

admin.use('/api/*', adminApiOnly);

admin.get('/', async (c) => {
  const settings = await getSettings(c.env);
  const [users, posts, comments, likes] = await Promise.all([
    c.env.DB.prepare('SELECT COUNT(*) AS count FROM wm_users').first<{ count: number }>(),
    c.env.DB.prepare('SELECT COUNT(*) AS count FROM wm_posts WHERE status != "trash"').first<{ count: number }>(),
    c.env.DB.prepare('SELECT COUNT(*) AS count FROM wm_comments WHERE status != "trash"').first<{ count: number }>(),
    c.env.DB.prepare('SELECT COUNT(*) AS count FROM wm_likes').first<{ count: number }>()
  ]);
  const trendRows = await all<{ day: string; posts: number }>(
    c.env.DB.prepare(
      `SELECT date(created_at, 'unixepoch') AS day, COUNT(*) AS posts
       FROM wm_posts
       WHERE created_at >= strftime('%s','now','-6 days')
       GROUP BY day
       ORDER BY day ASC`
    )
  );
  const days = Array.from({ length: 7 }, (_, i) => {
    const d = new Date();
    d.setDate(d.getDate() - (6 - i));
    return d.toISOString().slice(0, 10);
  });
  const trend = days.map((day) => ({ day, posts: trendRows.find((row) => row.day === day)?.posts || 0 }));
  return c.html(
    <AdminDashboard
      settings={settings}
      currentUser={c.get('currentUser')!}
      csrfToken={c.get('csrfToken')}
      stats={{ users: users?.count || 0, posts: posts?.count || 0, comments: comments?.count || 0, likes: likes?.count || 0, trend }}
    />
  );
});

admin.get('/users', async (c) => {
  const settings = await getSettings(c.env);
  const users = await all<{ id: string; username: string; email: string; display_name: string; role: string; status: string; created_at: number }>(
    c.env.DB.prepare('SELECT id, username, email, display_name, role, status, created_at FROM wm_users ORDER BY created_at DESC LIMIT 100')
  );
  return c.html(<AdminUsersPage settings={settings} currentUser={c.get('currentUser')!} csrfToken={c.get('csrfToken')} users={users} />);
});

admin.get('/posts', async (c) => {
  const settings = await getSettings(c.env);
  const posts = await all<{ id: string; title: string; content: string; display_name: string; visibility: string; status: string; is_top: number; is_ad: number; created_at: number }>(
    c.env.DB.prepare(
      `SELECT p.id, p.title, p.content, u.display_name, p.visibility, p.status, p.is_top, p.is_ad, p.created_at
       FROM wm_posts p JOIN wm_users u ON u.id = p.author_id
       ORDER BY p.created_at DESC LIMIT 100`
    )
  );
  return c.html(<AdminPostsPage settings={settings} currentUser={c.get('currentUser')!} csrfToken={c.get('csrfToken')} posts={posts} />);
});

admin.get('/comments', async (c) => {
  const settings = await getSettings(c.env);
  const comments = await all<{ id: string; post_id: string; content: string; status: string; author_name: string; created_at: number }>(
    c.env.DB.prepare(
      `SELECT c.id, c.post_id, c.content, c.status, COALESCE(u.display_name, c.guest_name, '访客') AS author_name, c.created_at
       FROM wm_comments c LEFT JOIN wm_users u ON u.id = c.user_id
       ORDER BY c.created_at DESC LIMIT 100`
    )
  );
  return c.html(<AdminCommentsPage settings={settings} currentUser={c.get('currentUser')!} csrfToken={c.get('csrfToken')} comments={comments} />);
});

admin.get('/links', async (c) => {
  const settings = await getSettings(c.env);
  const links = await listAdminLinks(c.env);
  return c.html(<AdminLinksPage settings={settings} currentUser={c.get('currentUser')!} csrfToken={c.get('csrfToken')} links={links as never} />);
});

admin.get('/settings', async (c) => {
  const settings = await getSettings(c.env);
  return c.html(<AdminSettingsPage settings={settings} currentUser={c.get('currentUser')!} csrfToken={c.get('csrfToken')} />);
});

admin.get('/audit', async (c) => {
  const settings = await getSettings(c.env);
  const logs = await all<{ id: number; actor_id: string | null; action: string; target: string | null; detail: string | null; ip: string | null; created_at: number }>(
    c.env.DB.prepare('SELECT id, actor_id, action, target, detail, ip, created_at FROM wm_audit_log ORDER BY created_at DESC, id DESC LIMIT 100')
  );
  return c.html(<AdminAuditPage settings={settings} currentUser={c.get('currentUser')!} csrfToken={c.get('csrfToken')} logs={logs} />);
});

admin.post('/api/users/:id/role', async (c) => {
  const input = await parseJson(c, z.object({ role: z.enum(['subscriber', 'author', 'admin']) }));
  await c.env.DB.prepare('UPDATE wm_users SET role = ?, updated_at = ? WHERE id = ?').bind(input.role, nowSeconds(), c.req.param('id')).run();
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'user.role', target: c.req.param('id'), detail: input, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { saved: true });
});

admin.post('/api/users/:id/ban', async (c) => {
  const row = await c.env.DB.prepare('SELECT status FROM wm_users WHERE id = ?').bind(c.req.param('id')).first<{ status: string }>();
  const next = row?.status === 'banned' ? 'active' : 'banned';
  await c.env.DB.prepare('UPDATE wm_users SET status = ?, updated_at = ? WHERE id = ?').bind(next, nowSeconds(), c.req.param('id')).run();
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'user.ban', target: c.req.param('id'), detail: { status: next }, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { status: next });
});

admin.post('/api/posts/bulk', async (c) => {
  const input = await parseJson(
    c,
    z.object({
      ids: z.array(z.string()).min(1).max(100),
      action: z.enum(['delete', 'topOn', 'topOff', 'adOn', 'adOff', 'publish', 'trash'])
    })
  );
  const placeholders = input.ids.map(() => '?').join(',');
  const sqlByAction: Record<typeof input.action, string> = {
    delete: `DELETE FROM wm_posts WHERE id IN (${placeholders})`,
    topOn: `UPDATE wm_posts SET is_top = 1 WHERE id IN (${placeholders})`,
    topOff: `UPDATE wm_posts SET is_top = 0 WHERE id IN (${placeholders})`,
    adOn: `UPDATE wm_posts SET is_ad = 1 WHERE id IN (${placeholders})`,
    adOff: `UPDATE wm_posts SET is_ad = 0 WHERE id IN (${placeholders})`,
    publish: `UPDATE wm_posts SET status = 'published' WHERE id IN (${placeholders})`,
    trash: `UPDATE wm_posts SET status = 'trash' WHERE id IN (${placeholders})`
  };
  await c.env.DB.prepare(sqlByAction[input.action]).bind(...input.ids).run();
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: `post.bulk.${input.action}`, detail: { ids: input.ids }, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { updated: input.ids.length });
});

admin.post('/api/settings', async (c) => {
  const json = await c.req.json().catch(() => ({}));
  const values: Record<string, unknown> = {};
  for (const key of SETTING_KEYS) if (Object.prototype.hasOwnProperty.call(json, key)) values[key] = json[key];
  const settings = await setSettings(c.env, values as never);
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'settings.update', detail: values, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { settings });
});

admin.post('/api/comments/:id/status', async (c) => {
  const input = await parseJson(c, z.object({ status: z.enum(['approved', 'pending', 'spam', 'trash']) }));
  await c.env.DB.prepare('UPDATE wm_comments SET status = ? WHERE id = ?').bind(input.status, c.req.param('id')).run();
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'comment.status', target: c.req.param('id'), detail: input, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { saved: true });
});

admin.delete('/api/comments/:id', async (c) => {
  await c.env.DB.prepare('DELETE FROM wm_comments WHERE id = ?').bind(c.req.param('id')).run();
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'comment.delete', target: c.req.param('id'), ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { deleted: true });
});

const linkSchema = z.object({
  name: z.string().min(1),
  url: z.string().url(),
  avatar: z.string().nullable().optional(),
  description: z.string().optional(),
  category: z.string().optional(),
  visible: z.boolean().optional(),
  sortOrder: z.number().int().optional()
});

admin.post('/api/links', async (c) => {
  const input = await parseJson(c, linkSchema);
  const id = await createLink(c.env, input);
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'link.create', target: id, detail: input, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { id }, 201);
});

admin.patch('/api/links/:id', async (c) => {
  const input = await parseJson(c, linkSchema.partial());
  await updateLink(c.env, c.req.param('id'), input);
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'link.update', target: c.req.param('id'), detail: input, ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { saved: true });
});

admin.delete('/api/links/:id', async (c) => {
  await deleteLink(c.env, c.req.param('id'));
  await audit(c.env.DB, { actorId: c.get('currentUser')?.id, action: 'link.delete', target: c.req.param('id'), ip: clientIp(c), createdAt: nowSeconds() });
  return apiOk(c, { deleted: true });
});
