/**
 * Auth routes: POST /login, POST /logout, GET /api/session.
 *
 * SECURITY:
 *  - /login is RATE-LIMITED per client IP BEFORE any credential work (brute-force throttle → 429).
 *  - FAIL-CLOSED: unset PANEL_PASSWORD ⇒ 503 (never authenticates); wrong password ⇒ 401.
 *  - credential compare is CONSTANT-TIME (compare.ts SHA-256 fixed-length).
 *  - on success, set a signed HttpOnly; SameSite=Strict; Secure-when-https session cookie (12h).
 *  - the password and session token are NEVER logged; input is Zod-validated at the boundary.
 */
import { z } from 'zod'
import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'
import type { PanelConfig } from '../config.js'
import { constantTimeEqual } from '../security/compare.js'
import { buildClearCookie, buildSetCookie } from '../security/cookies.js'
import { createSessionToken, SESSION_TTL_SEC } from '../security/session.js'
import { createSlidingWindowLimiter, DEFAULT_LOGIN_RATE_MAX, DEFAULT_LOGIN_RATE_WINDOW_MS, type RateLimiter } from '../security/rate-limit.js'
import { isSecureRequest, requestIsAuthed } from '../security/request.js'

const LoginBodySchema = z.object({ password: z.string().min(1).max(512) }).strict()

export interface AuthRoutesDeps {
  readonly config: PanelConfig
  readonly now?: () => number
  readonly rateLimiter?: RateLimiter
  readonly clientKey?: (req: FastifyRequest) => string
}

function setCookie(reply: FastifyReply, value: string): void {
  reply.header('set-cookie', value)
}

export function buildAuthRoutes(deps: AuthRoutesDeps): FastifyPluginAsync {
  const now = deps.now ?? (() => Date.now())
  const clientKey = deps.clientKey ?? ((req: FastifyRequest) => req.ip || 'unknown')
  const limiter = deps.rateLimiter ?? createSlidingWindowLimiter(DEFAULT_LOGIN_RATE_MAX, DEFAULT_LOGIN_RATE_WINDOW_MS, now)

  return async (app) => {
    app.post('/login', async (req, reply) => {
      // Throttle FIRST so brute force is limited regardless of outcome.
      if (!limiter.allow(clientKey(req))) {
        return reply.code(429).send({ error: 'rate_limited' })
      }

      const parsed = LoginBodySchema.safeParse(req.body)
      if (!parsed.success) return reply.code(400).send({ error: 'invalid request' })

      // Fail-closed: an unconfigured panel authenticates no one.
      const configured = deps.config.panelPassword
      if (typeof configured !== 'string' || configured.length === 0) {
        return reply.code(503).send({ error: 'login unavailable' })
      }

      if (!constantTimeEqual(parsed.data.password, configured)) {
        return reply.code(401).send({ error: 'rejected' })
      }

      const token = createSessionToken(deps.config.sessionSecret, now())
      setCookie(reply, buildSetCookie({ value: token, maxAgeSec: SESSION_TTL_SEC, secure: isSecureRequest(req) }))
      return reply.code(200).send({ authenticated: true })
    })

    app.post('/logout', async (req, reply) => {
      setCookie(reply, buildClearCookie({ secure: isSecureRequest(req) }))
      return reply.code(200).send({ authenticated: false })
    })

    app.get('/api/session', async (req, reply) => {
      return reply.code(200).send({ authenticated: requestIsAuthed(req, deps.config.sessionSecret, now()) })
    })
  }
}
