import { Hono } from 'hono';
import type { Context } from 'hono';
import { getCookie } from 'hono/cookie';
import { z } from 'zod';
import type { CurrentUser, Env, SiteSettings } from '../env';
import { createSession, destroySession, loginUser, registerUser, updatePassword } from '../services/auth';
import { getSettings } from '../services/settings';
import { enforceRateLimit } from '../services/ratelimit';
import { createPost, getPost, listPosts, softDeletePost, updatePost } from '../services/posts';
import { createComment, deleteComment, listComments } from '../services/comments';
import { likePost, listLikes, unlikePost } from '../services/likes';
import { confirmUpload, putSignedUpload, signUpload } from '../services/uploads';
import { listPublicLinks } from '../services/links';
import { first } from '../db/queries';
import { mediaUrl } from '../utils/image';
import { apiFail, apiOk, clearSessionCookie, publicUser, setSessionCookie } from './helpers';

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

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

function requireUser(user: CurrentUser | null): CurrentUser {
  if (!user) {
    const error = new Error('请先登录') as Error & { status: number; code: string };
    error.status = 401;
    error.code = 'UNAUTHENTICATED';
    throw error;
  }
  return user;
}

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;
}

const authRegisterSchema = z.object({
  username: z.string().regex(/^[a-zA-Z0-9_]{3,20}$/, '用户名需为 3-20 位字母数字或下划线'),
  email: z.string().email('邮箱格式不正确'),
  password: z.string().min(8, '密码至少 8 位'),
  displayName: z.string().min(1).max(24),
  bootstrapToken: z.string().optional()
});

const authLoginSchema = z.object({
  identifier: z.string().min(1),
  password: z.string().min(1)
});

api.post('/auth/register', async (c) => {
  await enforceRateLimit(c.env, `register:${ip(c)}`, 5, 60);
  const input = await parseJson(c, authRegisterSchema);
  const user = await registerUser(c.env, input);
  const session = await createSession(c.env, user.id, c.req.raw);
  setSessionCookie(c, session.token, session.maxAge);
  const settings = await getSettings(c.env);
  return apiOk(c, { user: publicUser(user, settings), csrfToken: c.get('csrfToken') });
});

api.post('/auth/login', async (c) => {
  await enforceRateLimit(c.env, `login:${ip(c)}`, 5, 60);
  const input = await parseJson(c, authLoginSchema);
  const user = await loginUser(c.env, input.identifier, input.password);
  const session = await createSession(c.env, user.id, c.req.raw);
  setSessionCookie(c, session.token, session.maxAge);
  const settings = await getSettings(c.env);
  return apiOk(c, { user: publicUser(user, settings), csrfToken: c.get('csrfToken') });
});

api.post('/auth/logout', async (c) => {
  const token = getCookie(c, c.env.COOKIE_NAME || 'wm_session') || '';
  await destroySession(c.env, token);
  clearSessionCookie(c);
  return apiOk(c, { loggedOut: true });
});

api.get('/auth/me', async (c) => {
  const settings = await getSettings(c.env);
  const user = c.get('currentUser');
  return apiOk(c, { user: user ? publicUser(user, settings) : null });
});

api.post('/auth/password', async (c) => {
  const user = requireUser(c.get('currentUser'));
  const input = await parseJson(c, z.object({ oldPassword: z.string(), newPassword: z.string().min(8) }));
  await updatePassword(c.env, user, input.oldPassword, input.newPassword);
  return apiOk(c, { changed: true });
});

api.get('/posts', async (c) => {
  const settings = await getSettings(c.env);
  const result = await listPosts(c.env, settings, {
    viewer: c.get('currentUser'),
    cursor: c.req.query('cursor'),
    limit: Number(c.req.query('limit') || '20'),
    authorUsername: c.req.query('author'),
    type: c.req.query('type') === 'article' ? 'article' : c.req.query('type') === 'moment' ? 'moment' : null,
    q: c.req.query('q')
  });
  return apiOk(c, result);
});

api.get('/posts/:id', async (c) => {
  const settings = await getSettings(c.env);
  const post = await getPost(c.env, settings, c.req.param('id'), c.get('currentUser'));
  if (!post) return apiFail(c, '内容不存在', 404, 'NOT_FOUND');
  return apiOk(c, { post });
});

const postSchema = z.object({
  type: z.enum(['moment', 'article']).default('moment'),
  visibility: z.enum(['public', 'private']).default('public'),
  isTop: z.boolean().default(false),
  isAd: z.boolean().default(false),
  content: z.string().min(1).max(5000),
  images: z.array(z.string()).max(9).default([]),
  videoKey: z.string().nullable().optional(),
  position: z.string().max(120).nullable().optional(),
  positionLat: z.number().nullable().optional(),
  positionLng: z.number().nullable().optional(),
  title: z.string().max(120).optional(),
  shareUrl: z.string().url().nullable().optional(),
  shareThumb: z.string().nullable().optional()
});

api.post('/posts', async (c) => {
  const user = requireUser(c.get('currentUser'));
  await enforceRateLimit(c.env, `post:${user.id}`, 10, 3600);
  const input = await parseJson(c, postSchema);
  const id = await createPost(c.env, user, {
    type: input.type || 'moment',
    visibility: input.visibility || 'public',
    isTop: input.isTop || false,
    isAd: input.isAd || false,
    content: input.content,
    images: input.images || [],
    videoKey: input.videoKey,
    position: input.position,
    positionLat: input.positionLat,
    positionLng: input.positionLng,
    title: input.title,
    shareUrl: input.shareUrl,
    shareThumb: input.shareThumb
  });
  const settings = await getSettings(c.env);
  const post = await getPost(c.env, settings, id, user);
  return apiOk(c, { post }, 201);
});

api.patch('/posts/:id', async (c) => {
  const user = requireUser(c.get('currentUser'));
  const input = await parseJson(c, postSchema.partial());
  await updatePost(c.env, c.req.param('id'), user, input);
  const settings = await getSettings(c.env);
  return apiOk(c, { post: await getPost(c.env, settings, c.req.param('id'), user, user.role === 'admin') });
});

api.delete('/posts/:id', async (c) => {
  const user = requireUser(c.get('currentUser'));
  await softDeletePost(c.env, c.req.param('id'), user);
  return apiOk(c, { deleted: true });
});

api.get('/posts/:id/edit', async (c) => {
  const user = requireUser(c.get('currentUser'));
  const settings = await getSettings(c.env);
  const post = await getPost(c.env, settings, c.req.param('id'), user, user.role === 'admin');
  if (!post || (post.author.id !== user.id && user.role !== 'admin')) return apiFail(c, '内容不存在', 404, 'NOT_FOUND');
  return apiOk(c, { post });
});

api.get('/posts/:id/comments', async (c) => {
  return apiOk(c, { comments: await listComments(c.env, c.req.param('id'), c.get('currentUser')) });
});

api.post('/comments', async (c) => {
  const settings = await getSettings(c.env);
  const user = c.get('currentUser');
  if (settings.comment_mode === 'closed') return apiFail(c, '评论已关闭', 403, 'COMMENTS_CLOSED');
  if (settings.comment_mode === 'registered' && !user) return apiFail(c, '登录后评论', 401, 'LOGIN_REQUIRED');
  if (user) await enforceRateLimit(c.env, `comment:${user.id}`, 30, 3600);
  else await enforceRateLimit(c.env, `comment:${ip(c)}`, 30, 3600);
  const input = await parseJson(
    c,
    z.object({
      postId: z.string().min(1),
      parentId: z.string().nullable().optional(),
      content: z.string().min(1).max(1000),
      guestName: z.string().max(40).nullable().optional(),
      guestEmail: z.string().email().nullable().optional(),
      guestUrl: z.string().url().nullable().optional()
    })
  );
  const id = await createComment(c.env, {
    ...input,
    user,
    status: settings.comment_review && user?.role !== 'admin' ? 'pending' : 'approved',
    ip: ip(c),
    userAgent: c.req.header('user-agent') || ''
  });
  return apiOk(c, { id, status: settings.comment_review ? 'pending' : 'approved' }, 201);
});

api.delete('/comments/:id', async (c) => {
  const user = requireUser(c.get('currentUser'));
  const deletedIds = await deleteComment(c.env, c.req.param('id'), user);
  return apiOk(c, { deletedIds });
});

api.post('/posts/:id/like', async (c) => {
  const user = requireUser(c.get('currentUser'));
  return apiOk(c, await likePost(c.env, c.req.param('id'), user));
});

api.delete('/posts/:id/like', async (c) => {
  const user = requireUser(c.get('currentUser'));
  return apiOk(c, await unlikePost(c.env, c.req.param('id'), user));
});

api.get('/posts/:id/likes', async (c) => {
  const settings = await getSettings(c.env);
  return apiOk(c, await listLikes(c.env, settings, c.req.param('id')));
});

api.post('/uploads/sign', async (c) => {
  const user = requireUser(c.get('currentUser'));
  const settings = await getSettings(c.env);
  const input = await parseJson(
    c,
    z.object({
      kind: z.enum(['image', 'video', 'avatar', 'site']),
      filename: z.string().min(1),
      size: z.number().int().positive(),
      mime: z.string().min(1)
    })
  );
  return apiOk(c, await signUpload(c.env, settings, user, input, new URL(c.req.url).origin));
});

api.put('/uploads/direct/:token', async (c) => {
  const meta = await putSignedUpload(c.env, c.req.param('token'), c.req.raw);
  return apiOk(c, { key: meta.key, uploaded: true });
});

api.post('/uploads/confirm', async (c) => {
  requireUser(c.get('currentUser'));
  const input = await parseJson(c, z.object({ key: z.string().min(1) }));
  return apiOk(c, await confirmUpload(c.env, input.key));
});

api.get('/links', async (c) => {
  const settings = await getSettings(c.env);
  return apiOk(c, { links: await listPublicLinks(c.env, settings) });
});

api.get('/users/:username', async (c) => {
  const settings = await getSettings(c.env);
  const row = await first<{ id: string; username: string; display_name: string; avatar_key: string | null; bio: string; url: string; role: string }>(
    c.env.DB.prepare('SELECT id, username, display_name, avatar_key, bio, url, role FROM wm_users WHERE username = ? AND status = "active"').bind(c.req.param('username'))
  );
  if (!row) return apiFail(c, '用户不存在', 404, 'NOT_FOUND');
  return apiOk(c, {
    user: {
      id: row.id,
      username: row.username,
      displayName: row.display_name,
      avatarUrl: mediaUrl(settings.cdn_base_url, row.avatar_key),
      bio: row.bio || '',
      url: row.url || '',
      role: row.role
    }
  });
});

api.patch('/users/me', async (c) => {
  const user = requireUser(c.get('currentUser'));
  const input = await parseJson(
    c,
    z.object({
      displayName: z.string().min(1).max(24).optional(),
      avatarKey: z.string().nullable().optional(),
      bio: z.string().max(240).optional(),
      url: z.string().url().or(z.literal('')).optional()
    })
  );
  await c.env.DB.prepare(
    `UPDATE wm_users SET
      display_name = COALESCE(?, display_name),
      avatar_key = COALESCE(?, avatar_key),
      bio = COALESCE(?, bio),
      url = COALESCE(?, url),
      updated_at = strftime('%s','now')
     WHERE id = ?`
  )
    .bind(input.displayName ?? null, input.avatarKey ?? null, input.bio ?? null, input.url ?? null, user.id)
    .run();
  const settings = await getSettings(c.env);
  const fresh = await first<{ id: string; username: string; email: string; display_name: string; avatar_key: string | null; bio: string; url: string; role: string }>(
    c.env.DB.prepare('SELECT id, username, email, display_name, avatar_key, bio, url, role FROM wm_users WHERE id = ?').bind(user.id)
  );
  return apiOk(c, { user: fresh ? { ...fresh, avatarUrl: mediaUrl(settings.cdn_base_url, fresh.avatar_key) } : null });
});

api.get('/geo/lookup', async (c) => {
  const settings: SiteSettings = await getSettings(c.env);
  const lat = c.req.query('lat');
  const lng = c.req.query('lng');
  const key = settings.tencent_map_key || c.env.TENCENT_MAP_KEY;
  if (!key || !lat || !lng) return apiOk(c, { position: '' });
  const res = await fetch(`https://apis.map.qq.com/ws/geocoder/v1/?location=${encodeURIComponent(`${lat},${lng}`)}&key=${encodeURIComponent(key)}`);
  const data = (await res.json()) as { result?: { address_component?: { city?: string }; formatted_addresses?: { recommend?: string } } };
  const city = data.result?.address_component?.city || '';
  const place = data.result?.formatted_addresses?.recommend || '';
  return apiOk(c, { position: [city, place].filter(Boolean).join('·') });
});
