import { SignJWT, jwtVerify } from 'jose'
import bcrypt from 'bcryptjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
import pool from './db'
import { hashToken, generateToken, getClientIp, logAudit } from './auth'

const ADMIN_COOKIE_NAME = 'pitch_admin_session'
const ADMIN_JWT_AUDIENCE = 'pitch-admin'
const ADMIN_JWT_EXPIRY = '2h'
const ADMIN_SESSION_EXPIRY_HOURS = 12

function getJwtSecret() {
  const secret = process.env.PITCH_JWT_SECRET
  if (!secret) throw new Error('PITCH_JWT_SECRET not set')
  return new TextEncoder().encode(secret)
}

export interface Admin {
  id: string
  email: string
  name: string
  is_active: boolean
  last_login_at: string | null
  created_at: string
}

export interface AdminJwtPayload {
  sub: string         // admin id
  email: string
  sessionId: string
}

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 12)
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash)
}

export async function createAdminJwt(payload: AdminJwtPayload): Promise<string> {
  return new SignJWT({ ...payload })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime(ADMIN_JWT_EXPIRY)
    .setAudience(ADMIN_JWT_AUDIENCE)
    .sign(getJwtSecret())
}

export async function verifyAdminJwt(token: string): Promise<AdminJwtPayload | null> {
  try {
    const { payload } = await jwtVerify(token, getJwtSecret(), { audience: ADMIN_JWT_AUDIENCE })
    return payload as unknown as AdminJwtPayload
  } catch {
    return null
  }
}

export async function createAdminSession(
  adminId: string,
  ip: string | null,
  userAgent: string | null,
): Promise<{ sessionId: string; jwt: string }> {
  // Single session per admin
  await pool.query(
    `UPDATE pitch_admin_sessions SET revoked = true WHERE admin_id = $1 AND revoked = false`,
    [adminId],
  )

  const sessionToken = generateToken()
  const tokenHash = hashToken(sessionToken)
  const expiresAt = new Date(Date.now() + ADMIN_SESSION_EXPIRY_HOURS * 60 * 60 * 1000)

  const { rows } = await pool.query(
    `INSERT INTO pitch_admin_sessions (admin_id, token_hash, ip_address, user_agent, expires_at)
     VALUES ($1, $2, $3, $4, $5) RETURNING id`,
    [adminId, tokenHash, ip, userAgent, expiresAt],
  )

  const sessionId = rows[0].id

  const adminRes = await pool.query(`SELECT email FROM pitch_admins WHERE id = $1`, [adminId])
  const jwt = await createAdminJwt({
    sub: adminId,
    email: adminRes.rows[0].email,
    sessionId,
  })

  return { sessionId, jwt }
}

export async function validateAdminSession(sessionId: string, adminId: string): Promise<boolean> {
  const { rows } = await pool.query(
    `SELECT s.id FROM pitch_admin_sessions s
     JOIN pitch_admins a ON a.id = s.admin_id
     WHERE s.id = $1 AND s.admin_id = $2 AND s.revoked = false AND s.expires_at > NOW() AND a.is_active = true`,
    [sessionId, adminId],
  )
  return rows.length > 0
}

export async function revokeAdminSession(sessionId: string): Promise<void> {
  await pool.query(`UPDATE pitch_admin_sessions SET revoked = true WHERE id = $1`, [sessionId])
}

export async function revokeAllAdminSessions(adminId: string): Promise<void> {
  await pool.query(`UPDATE pitch_admin_sessions SET revoked = true WHERE admin_id = $1`, [adminId])
}

export async function setAdminCookie(jwt: string): Promise<void> {
  const cookieStore = await cookies()
  cookieStore.set(ADMIN_COOKIE_NAME, jwt, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    path: '/',
    maxAge: ADMIN_SESSION_EXPIRY_HOURS * 60 * 60,
  })
}

export async function clearAdminCookie(): Promise<void> {
  const cookieStore = await cookies()
  cookieStore.delete(ADMIN_COOKIE_NAME)
}

export async function getAdminPayloadFromCookie(): Promise<AdminJwtPayload | null> {
  const cookieStore = await cookies()
  const token = cookieStore.get(ADMIN_COOKIE_NAME)?.value
  if (!token) return null
  return verifyAdminJwt(token)
}

/**
 * Server-side: read the admin row from the cookie. Returns null if no valid session
 * or the admin is inactive. Use in layout.tsx and API routes.
 */
export async function getAdminFromCookie(): Promise<Admin | null> {
  const payload = await getAdminPayloadFromCookie()
  if (!payload) return null

  const valid = await validateAdminSession(payload.sessionId, payload.sub)
  if (!valid) return null

  const { rows } = await pool.query(
    `SELECT id, email, name, is_active, last_login_at, created_at
     FROM pitch_admins WHERE id = $1`,
    [payload.sub],
  )
  if (rows.length === 0 || !rows[0].is_active) return null
  return rows[0] as Admin
}

/**
 * API guard: returns the Admin row, OR a NextResponse 401/403 to return early.
 * Also accepts the legacy PITCH_ADMIN_SECRET bearer header for CLI/automation —
 * in that case the returned admin id is null but the request is allowed.
 */
export type AdminGuardResult =
  | { kind: 'admin'; admin: Admin }
  | { kind: 'cli' }
  | { kind: 'response'; response: NextResponse }

export async function requireAdmin(request: Request): Promise<AdminGuardResult> {
  // CLI fallback via shared secret
  const secret = process.env.PITCH_ADMIN_SECRET
  if (secret) {
    const auth = request.headers.get('authorization')
    if (auth === `Bearer ${secret}`) {
      return { kind: 'cli' }
    }
  }

  const admin = await getAdminFromCookie()
  if (!admin) {
    return {
      kind: 'response',
      response: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }),
    }
  }
  return { kind: 'admin', admin }
}

/**
 * Convenience: log an admin-initiated audit event. Falls back to CLI actor when admin is null.
 */
export async function logAdminAudit(
  adminId: string | null,
  action: string,
  details: Record<string, unknown> = {},
  request?: Request,
  targetInvestorId?: string | null,
): Promise<void> {
  await logAudit(
    null,           // investor_id
    action,
    details,
    request,
    undefined,      // slide_id
    undefined,      // session_id
    adminId,
    targetInvestorId ?? null,
  )
}

export { ADMIN_COOKIE_NAME }
