import bcrypt from 'bcryptjs'
import crypto from 'crypto'
import jwt from 'jsonwebtoken'
import { signActorToken } from '../../security/tokens'
import { AppError } from '../../http/errors'
import { sendTransactionalEmail } from '../../services/notificationService'
import { resetPasswordEmail, type Lang } from '../../lib/emailTranslations'
import { presentEmployeeSession } from './auth.presenter'
import * as repo from './auth.employee.repo'
import { employeeLanguageSchema, employeeLoginSchema } from './auth.employee.schemas'
import type { output } from 'zod'

const RESET_TOKEN_TTL_MINUTES = 60

type EmployeeLoginInput = output<typeof employeeLoginSchema>
type EmployeeLanguageInput = output<typeof employeeLanguageSchema>
type EmployeePasswordResetPayload = jwt.JwtPayload & {
  sub: string
  type: 'employee_password_reset'
  pwdv: string
}

function signEmployeeToken(employeeId: string) {
  return signActorToken(employeeId, 'employee', {
    expiresIn: (process.env.JWT_EXPIRY ?? '8h') as jwt.SignOptions['expiresIn'],
  })
}

function getEmployeePasswordResetVersion(passwordHash: string | null | undefined) {
  return crypto
    .createHash('sha256')
    .update(`${process.env.JWT_SECRET!}:${passwordHash ?? 'no-password-set'}`)
    .digest('hex')
}

function signEmployeePasswordResetToken(employeeId: string, passwordHash: string | null | undefined) {
  return jwt.sign(
    {
      sub: employeeId,
      type: 'employee_password_reset',
      pwdv: getEmployeePasswordResetVersion(passwordHash),
    },
    process.env.JWT_SECRET!,
    {
      algorithm: 'HS256',
      issuer: 'rentaldrivego-api',
      audience: 'employee_password_reset',
      expiresIn: `${RESET_TOKEN_TTL_MINUTES}m`,
    },
  )
}

function verifyEmployeePasswordResetToken(token: string): EmployeePasswordResetPayload | null {
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!, {
      algorithms: ['HS256'],
      issuer: 'rentaldrivego-api',
      audience: 'employee_password_reset',
    }) as jwt.JwtPayload

    if (
      typeof payload.sub !== 'string' ||
      payload.type !== 'employee_password_reset' ||
      typeof payload.pwdv !== 'string'
    ) {
      return null
    }

    return payload as EmployeePasswordResetPayload
  } catch {
    return null
  }
}

function ensureDashboardBasePath(baseUrl: string) {
  try {
    const url = new URL(baseUrl)
    const pathname = url.pathname.replace(/\/$/, '')
    if (!pathname.endsWith('/dashboard')) url.pathname = `${pathname}/dashboard`
    return url.toString().replace(/\/$/, '')
  } catch {
    const trimmed = baseUrl.replace(/\/$/, '')
    return trimmed.endsWith('/dashboard') ? trimmed : `${trimmed}/dashboard`
  }
}

export async function getMe(employeeId: string) {
  const employee = await repo.findEmployeeWithCompanyById(employeeId)

  if (!employee || !employee.isActive) {
    throw new AppError('Employee account not found or inactive', 401, 'unauthenticated')
  }

  return presentEmployeeSession(employee)
}

export async function login(body: EmployeeLoginInput) {
  const employee = await repo.findEmployeeWithCompanyByEmail(body.email)

  if (!employee || !employee.isActive) {
    throw new AppError('Invalid email or password', 401, 'invalid_credentials')
  }

  if (!employee.passwordHash) {
    throw new AppError('This account does not have a password yet. Use the invitation or reset email to set one first.', 401, 'password_not_set')
  }

  if (!employee.emailVerified && employee.emailVerificationToken) {
    throw new AppError('Please verify your email address before signing in. Check your inbox for the verification link.', 401, 'email_not_verified')
  }

  const validPassword = await bcrypt.compare(body.password, employee.passwordHash)
  if (!validPassword) {
    throw new AppError('Invalid email or password', 401, 'invalid_credentials')
  }

  return presentEmployeeSession(employee, signEmployeeToken(employee.id))
}

export async function forgotPassword(email: string) {
  const employee = await repo.findActiveEmployeeByEmail(email)

  if (employee) {
    const resetToken = signEmployeePasswordResetToken(employee.id, employee.passwordHash)

    const dashboardUrl = ensureDashboardBasePath(
      process.env.DASHBOARD_URL ?? process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard',
    )
    const resetUrl = `${dashboardUrl}/reset-password?token=${encodeURIComponent(resetToken)}`
    const lang = (employee.preferredLanguage as Lang) ?? 'fr'

    await sendTransactionalEmail({
      to: employee.email,
      subject: resetPasswordEmail.subject(lang),
      html: resetPasswordEmail.html(resetUrl, employee.firstName, RESET_TOKEN_TTL_MINUTES, lang),
      text: resetPasswordEmail.text(resetUrl, employee.firstName, RESET_TOKEN_TTL_MINUTES, lang),
    }).catch((err) => {
      console.error('[ForgotPassword] Email delivery failed:', err?.message)
      console.error('[ForgotPassword] Provider config — resendKey present:', !!process.env.RESEND_API_KEY, '| smtpHost:', process.env.MAIL_HOST ?? 'not set')
    })
  }

  return { message: 'If that email is registered, a reset link has been sent.' }
}

export async function updateLanguage(employeeId: string, language: EmployeeLanguageInput['language']) {
  await repo.updatePreferredLanguage(employeeId, language)
  return { language }
}

async function findEmployeeFromResetToken(token: string) {
  try {
    const employee = await repo.findEmployeeByResetToken(token)
    if (employee) return employee
  } catch (err: any) {
    console.error('[EmployeeResetPassword] Stored token lookup failed:', err?.message ?? String(err))
  }

  const payload = verifyEmployeePasswordResetToken(token)
  if (!payload) return null

  const employee = await repo.findEmployeeById(payload.sub)
  if (!employee || !employee.isActive) return null

  if (payload.pwdv !== getEmployeePasswordResetVersion(employee.passwordHash)) {
    return null
  }

  return employee
}

export async function resetPassword(token: string, password: string) {
  const employee = await findEmployeeFromResetToken(token)

  if (!employee) {
    throw new AppError('Reset link is invalid or has expired.', 400, 'invalid_token')
  }

  await repo.resetPassword(employee.id, await bcrypt.hash(password, 12))
  return { message: 'Password updated successfully. You can now sign in.' }
}

export async function verifyEmail(token: string) {
  const employee = await repo.findEmployeeByVerificationToken(token)

  if (!employee) {
    throw new AppError('Verification link is invalid or has expired.', 400, 'invalid_token')
  }

  await repo.verifyEmployeeEmail(employee.id)

  return { message: 'Email verified successfully. You can now sign in.', email: employee.email }
}

export async function resendVerification(email: string) {
  const employee = await repo.findActiveEmployeeByEmail(email)

  if (!employee) {
    return { message: 'If that email is registered and not yet verified, a new verification link has been sent.' }
  }

  if (employee.emailVerified) {
    return { message: 'This email is already verified. You can sign in.' }
  }

  const verificationToken = crypto.randomBytes(32).toString('hex')
  await repo.setEmailVerificationToken(employee.id, verificationToken)

  const dashboardUrl = ensureDashboardBasePath(
    process.env.DASHBOARD_URL ?? process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard',
  )
  const verifyUrl = `${dashboardUrl}/verify-email?token=${encodeURIComponent(verificationToken)}`

  const lang: 'en' | 'fr' | 'ar' =
    employee.preferredLanguage === 'ar' || employee.preferredLanguage === 'fr'
      ? employee.preferredLanguage
      : 'en'

  const emailSubjects: Record<'en' | 'fr' | 'ar', string> = {
    en: 'Verify your email — RentalDriveGo',
    fr: 'Vérifiez votre email — RentalDriveGo',
    ar: 'تحقق من بريدك الإلكتروني — RentalDriveGo',
  }

  await sendTransactionalEmail({
    to: employee.email,
    subject: emailSubjects[lang],
    html: `<p>Click the link below to verify your email and activate your account:</p><p><a href="${verifyUrl}">Verify Email</a></p>`,
    text: `Verify your email by visiting:\n${verifyUrl}`,
  }).catch((err) => {
    console.error('[ResendVerification] Email delivery failed:', err?.message ?? String(err))
    console.error('[ResendVerification] SMTP config — host:', process.env.MAIL_HOST ?? 'not set', '| port:', process.env.MAIL_PORT ?? 'not set', '| user:', process.env.MAIL_USERNAME ? '***' : 'not set')
    console.error('[ResendVerification] Resend config — apiKey:', process.env.RESEND_API_KEY ? (process.env.RESEND_API_KEY.startsWith('re_') ? 'valid' : 'placeholder') : 'not set')
  })

  return { message: 'A new verification link has been sent to your email.' }
}
