import { SignJWT, jwtVerify, decodeJwt } from 'jose'
import { randomBytes, createHash } from 'crypto'
import { cookies } from 'next/headers'
import pool from './db'

const COOKIE_NAME = 'pitch_session'
const SESSION_EXPIRY_HOURS = 24
const JWT_EXPIRY = `${SESSION_EXPIRY_HOURS}h`

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 function hashToken(token: string): string {
  return createHash('sha256').update(token).digest('hex')
}

export function generateToken(): string {
  return randomBytes(48).toString('hex')
}

export interface JwtPayload {
  sub: string
  email: string
  sessionId: string
}

export async function createJwt(payload: JwtPayload): Promise<string> {
  return new SignJWT({ ...payload })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime(JWT_EXPIRY)
    .sign(getJwtSecret())
}

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

export async function createSession(
  investorId: string,
  ip: string | null,
  userAgent: string | null
): Promise<{ sessionId: string; jwt: string }> {
  // Revoke all existing sessions for this investor (single session enforcement)
  await pool.query(
    `UPDATE pitch_sessions SET revoked = true WHERE investor_id = $1 AND revoked = false`,
    [investorId]
  )

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

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

  const sessionId = rows[0].id

  // Get investor email for JWT
  const investor = await pool.query(
    `SELECT email FROM pitch_investors WHERE id = $1`,
    [investorId]
  )

  const jwt = await createJwt({
    sub: investorId,
    email: investor.rows[0].email,
    sessionId,
  })

  return { sessionId, jwt }
}

export async function validateSession(sessionId: string, investorId: string): Promise<boolean> {
  const { rows } = await pool.query(
    `SELECT id FROM pitch_sessions
     WHERE id = $1 AND investor_id = $2 AND revoked = false AND expires_at > NOW()`,
    [sessionId, investorId]
  )
  return rows.length > 0
}

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

export async function revokeAllSessions(investorId: string): Promise<void> {
  await pool.query(
    `UPDATE pitch_sessions SET revoked = true WHERE investor_id = $1`,
    [investorId]
  )
}

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

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

export async function getSessionFromCookie(): Promise<JwtPayload | null> {
  const cookieStore = await cookies()
  const token = cookieStore.get(COOKIE_NAME)?.value
  if (!token) return null

  // Fast path: valid non-expired JWT
  const payload = await verifyJwt(token)
  if (payload) return payload

  // Slow path: JWT may be expired but DB session could still be valid.
  // Decode without signature/expiry check to recover sessionId + sub.
  try {
    const decoded = decodeJwt(token) as Partial<JwtPayload>
    if (!decoded.sessionId || !decoded.sub) return null

    const valid = await validateSession(decoded.sessionId, decoded.sub)
    if (!valid) return null

    // DB session still live — fetch email and reissue a fresh JWT
    const { rows } = await pool.query(
      `SELECT email FROM pitch_investors WHERE id = $1`,
      [decoded.sub]
    )
    if (rows.length === 0) return null

    const freshJwt = await createJwt({ sub: decoded.sub, email: rows[0].email, sessionId: decoded.sessionId })
    await setSessionCookie(freshJwt)

    return { sub: decoded.sub, email: rows[0].email, sessionId: decoded.sessionId }
  } catch {
    return null
  }
}

export function getClientIp(request: Request): string | null {
  const forwarded = request.headers.get('x-forwarded-for')
  if (forwarded) return forwarded.split(',')[0].trim()
  return null
}

export function validateAdminSecret(request: Request): boolean {
  const secret = process.env.PITCH_ADMIN_SECRET
  if (!secret) return false
  const auth = request.headers.get('authorization')
  if (!auth) return false
  return auth === `Bearer ${secret}`
}

export async function logAudit(
  investorId: string | null,
  action: string,
  details: Record<string, unknown> = {},
  request?: Request,
  slideId?: string,
  sessionId?: string,
  adminId?: string | null,
  targetInvestorId?: string | null,
): Promise<void> {
  const ip = request ? getClientIp(request) : null
  const ua = request ? request.headers.get('user-agent') : null
  await pool.query(
    `INSERT INTO pitch_audit_logs
       (investor_id, action, details, ip_address, user_agent, slide_id, session_id, admin_id, target_investor_id)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
    [investorId, action, JSON.stringify(details), ip, ua, slideId, sessionId, adminId ?? null, targetInvestorId ?? null]
  )
}
