import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'
import { env } from '../../config/env'
import { AuthRepository } from './auth.repository'
import { dragonfly } from '../../infra/cache/dragonfly'

const BCRYPT_ROUNDS = 12
const ACCESS_EXPIRES = '8h'
const REFRESH_EXPIRES = '30d'
const REFRESH_TTL_SECONDS = 30 * 24 * 60 * 60

interface TokenPair {
  accessToken: string
  refreshToken: string
}

interface JwtPayload {
  sub: string
  role: string
  type: 'access' | 'refresh'
}

export class AuthService {
  private repo = new AuthRepository()

  // ─── Register ─────────────────────────────────────────────────────────────

  async register(input: {
    name: string
    email: string
    password: string
    planId?: string
  }) {
    const existing = await this.repo.findByEmail(input.email)
    if (existing) {
      throw Object.assign(new Error('E-mail já cadastrado'), { statusCode: 409 })
    }

    const passwordHash = await bcrypt.hash(input.password, BCRYPT_ROUNDS)

    const user = await this.repo.create({
      name: input.name,
      email: input.email,
      passwordHash,
      planId: input.planId,
    })

    const tokens = this.issueTokens(user.id, user.role)
    await this.saveRefreshToken(user.id, tokens.refreshToken)

    return { user, ...tokens }
  }

  // ─── Login ────────────────────────────────────────────────────────────────

  async login(input: { email: string; password: string }) {
    const user = await this.repo.findByEmail(input.email)
    if (!user) {
      throw Object.assign(new Error('Credenciais inválidas'), { statusCode: 401 })
    }

    if (!user.isActive) {
      throw Object.assign(new Error('Conta desativada'), { statusCode: 403 })
    }

    const valid = await bcrypt.compare(input.password, user.passwordHash)
    if (!valid) {
      throw Object.assign(new Error('Credenciais inválidas'), { statusCode: 401 })
    }

    const tokens = this.issueTokens(user.id, user.role)
    await this.saveRefreshToken(user.id, tokens.refreshToken)

    const { passwordHash: _, ...safeUser } = user
    return { user: safeUser, ...tokens }
  }

  // ─── Refresh ──────────────────────────────────────────────────────────────

  async refresh(refreshToken: string) {
    let payload: JwtPayload
    try {
      payload = jwt.verify(refreshToken, env.JWT_SECRET) as JwtPayload
    } catch {
      throw Object.assign(new Error('Refresh token inválido'), { statusCode: 401 })
    }

    if (payload.type !== 'refresh') {
      throw Object.assign(new Error('Token incorreto'), { statusCode: 401 })
    }

    // Valida que o token ainda está no cache (não foi revogado)
    const stored = await dragonfly.get(this.refreshKey(payload.sub))
    if (stored !== refreshToken) {
      throw Object.assign(new Error('Refresh token expirado ou revogado'), { statusCode: 401 })
    }

    const user = await this.repo.findById(payload.sub)
    if (!user || !user.isActive) {
      throw Object.assign(new Error('Usuário inativo'), { statusCode: 403 })
    }

    const tokens = this.issueTokens(user.id, user.role)
    await this.saveRefreshToken(user.id, tokens.refreshToken)

    return { user, ...tokens }
  }

  // ─── Logout ───────────────────────────────────────────────────────────────

  async logout(userId: string) {
    await dragonfly.del(this.refreshKey(userId))
  }

  // ─── Me ───────────────────────────────────────────────────────────────────

  async me(userId: string) {
    const user = await this.repo.findById(userId)
    if (!user) {
      throw Object.assign(new Error('Usuário não encontrado'), { statusCode: 404 })
    }

    const trialEndsAt = user.trialEndsAt ? new Date(user.trialEndsAt) : null
    const daysRemaining = trialEndsAt
      ? Math.max(0, Math.ceil((trialEndsAt.getTime() - Date.now()) / 86_400_000))
      : null

    return { ...user, daysRemaining }
  }

  // ─── Change Password ──────────────────────────────────────────────────────

  async changePassword(userId: string, input: { currentPassword: string; newPassword: string }) {
    const user = await this.repo.findByEmail(
      (await this.repo.findById(userId))?.email ?? ''
    )
    if (!user) throw Object.assign(new Error('Usuário não encontrado'), { statusCode: 404 })

    const valid = await bcrypt.compare(input.currentPassword, user.passwordHash)
    if (!valid) {
      throw Object.assign(new Error('Senha atual incorreta'), { statusCode: 400 })
    }

    const newHash = await bcrypt.hash(input.newPassword, BCRYPT_ROUNDS)
    await this.repo.updatePassword(userId, newHash)
    await this.logout(userId) // invalida todas as sessões
  }

  // ─── Helpers ──────────────────────────────────────────────────────────────

  private issueTokens(userId: string, role: string): TokenPair {
    const base = { sub: userId, role }

    const accessToken = jwt.sign(
      { ...base, type: 'access' },
      env.JWT_SECRET,
      { expiresIn: ACCESS_EXPIRES }
    )

    const refreshToken = jwt.sign(
      { ...base, type: 'refresh' },
      env.JWT_SECRET,
      { expiresIn: REFRESH_EXPIRES }
    )

    return { accessToken, refreshToken }
  }

  private async saveRefreshToken(userId: string, token: string) {
    await dragonfly.set(this.refreshKey(userId), token, REFRESH_TTL_SECONDS)
  }

  private refreshKey(userId: string): string {
    return `auth:refresh:${userId}`
  }
}
