// Signup, login, refresh (rotating), logout, and me.
import { Router } from 'express';
import crypto from 'node:crypto';
import { ulid } from 'ulid';
import { z } from 'zod';
import { queryOne, execute } from '../db/client.js';
import { hashPassword, verifyPassword } from '../utils/password.js';
import { signAccessToken } from '../utils/jwt.js';
import { requireAuth } from '../middleware/auth.js';
import { HttpError } from '../middleware/error.js';
import { getSubscriptionStatus } from '../middleware/subscription.js';
import { config } from '../config.js';

const router = Router();
const now = () => Math.floor(Date.now() / 1000);
const hashToken = (raw) => crypto.createHash('sha256').update(raw).digest('hex');

function publicUser(u) {
  return { id: u.id, email: u.email, display_name: u.display_name, role: u.role, avatar_url: u.avatar_url || null };
}

async function issueRefreshToken(userId) {
  const raw = crypto.randomBytes(48).toString('hex');
  await execute(
    `INSERT INTO refresh_tokens (id, user_id, token_hash, expires_at, created_at) VALUES (?, ?, ?, ?, ?)`,
    [ulid(), userId, hashToken(raw), now() + config.refreshTtlSec, now()]
  );
  return raw;
}

function zodFail(err, next) {
  if (err instanceof z.ZodError) {
    return next(new HttpError(400, 'invalid_input', err.issues[0]?.message || 'Invalid input'));
  }
  next(err);
}

const signupSchema = z.object({
  email: z.string().email().max(200),
  password: z.string().min(8, 'Password must be at least 8 characters').max(200),
  display_name: z.string().min(1, 'Name is required').max(80),
});

router.post('/signup', async (req, res, next) => {
  try {
    const data = signupSchema.parse(req.body);
    const email = data.email.toLowerCase();
    const existing = await queryOne(`SELECT id FROM users WHERE email = ?`, [email]);
    if (existing) throw new HttpError(409, 'email_taken', 'An account with that email already exists');

    const id = ulid();
    const pwHash = await hashPassword(data.password);
    await execute(
      `INSERT INTO users (id, email, password_hash, display_name, role, created_at, updated_at)
       VALUES (?, ?, ?, ?, 'member', ?, ?)`,
      [id, email, pwHash, data.display_name, now(), now()]
    );

    const user = { id, email, display_name: data.display_name, role: 'member', avatar_url: null };
    const access = signAccessToken(user);
    const refresh = await issueRefreshToken(id);
    res.status(201).json({ access_token: access, refresh_token: refresh, user: publicUser(user) });
  } catch (err) {
    zodFail(err, next);
  }
});

const loginSchema = z.object({ email: z.string().email(), password: z.string() });

router.post('/login', async (req, res, next) => {
  try {
    const data = loginSchema.parse(req.body);
    const user = await queryOne(`SELECT * FROM users WHERE email = ?`, [data.email.toLowerCase()]);
    if (!user) throw new HttpError(401, 'invalid_credentials', 'Email or password is incorrect');
    const ok = await verifyPassword(data.password, user.password_hash);
    if (!ok) throw new HttpError(401, 'invalid_credentials', 'Email or password is incorrect');

    const access = signAccessToken(user);
    const refresh = await issueRefreshToken(user.id);
    res.json({ access_token: access, refresh_token: refresh, user: publicUser(user) });
  } catch (err) {
    zodFail(err, next);
  }
});

const refreshSchema = z.object({ refresh_token: z.string() });

router.post('/refresh', async (req, res, next) => {
  try {
    const { refresh_token } = refreshSchema.parse(req.body);
    const row = await queryOne(`SELECT * FROM refresh_tokens WHERE token_hash = ?`, [hashToken(refresh_token)]);
    if (!row || row.revoked_at || row.expires_at < now()) {
      throw new HttpError(401, 'invalid_token', 'Please sign in again');
    }
    // Rotate: revoke the used token, issue a fresh pair.
    await execute(`UPDATE refresh_tokens SET revoked_at = ? WHERE id = ?`, [now(), row.id]);
    const user = await queryOne(`SELECT * FROM users WHERE id = ?`, [row.user_id]);
    if (!user) throw new HttpError(401, 'invalid_token', 'Please sign in again');

    const access = signAccessToken(user);
    const newRefresh = await issueRefreshToken(user.id);
    res.json({ access_token: access, refresh_token: newRefresh });
  } catch (err) {
    zodFail(err, next);
  }
});

router.post('/logout', async (req, res, next) => {
  try {
    const token = req.body?.refresh_token;
    if (token) {
      await execute(`UPDATE refresh_tokens SET revoked_at = ? WHERE token_hash = ?`, [now(), hashToken(token)]);
    }
    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
});

router.get('/me', requireAuth, async (req, res, next) => {
  try {
    const user = await queryOne(
      `SELECT id, email, display_name, role, avatar_url, bio, created_at FROM users WHERE id = ?`,
      [req.auth.userId]
    );
    if (!user) throw new HttpError(404, 'not_found', 'User not found');
    const subscription = await getSubscriptionStatus(user.id);
    res.json({ user, subscription });
  } catch (err) {
    next(err);
  }
});

export default router;
