import bcrypt from 'bcryptjs';
import jwt, { SignOptions } from 'jsonwebtoken';
import crypto from 'crypto';
import { CcpRole } from '@prisma/client';
import { prisma } from '../../lib/prisma';
import { env } from '../../config/env';
import { AppError } from '../../middleware/error-handler';

interface TokenPayload {
  id: string;
  email: string;
  role: CcpRole;
}

function signAccessToken(payload: TokenPayload): string {
  return jwt.sign(payload, env.JWT_ACCESS_SECRET, {
    expiresIn: env.JWT_ACCESS_EXPIRES_IN as SignOptions['expiresIn'],
  });
}

function signRefreshToken(payload: TokenPayload): string {
  return jwt.sign(payload, env.JWT_REFRESH_SECRET, {
    expiresIn: env.JWT_REFRESH_EXPIRES_IN as SignOptions['expiresIn'],
  });
}

function parseExpiry(expiresIn: string): Date {
  const match = expiresIn.match(/^(\d+)([smhd])$/);
  if (!match) return new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // default 7d
  const [, num, unit] = match;
  const multipliers: Record<string, number> = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
  return new Date(Date.now() + parseInt(num) * multipliers[unit]);
}

export async function login(email: string, password: string) {
  const user = await prisma.ccpUser.findUnique({ where: { email } });
  if (!user) {
    throw new AppError(401, 'Invalid credentials', 'INVALID_CREDENTIALS');
  }

  const valid = await bcrypt.compare(password, user.password);
  if (!valid) {
    throw new AppError(401, 'Invalid credentials', 'INVALID_CREDENTIALS');
  }

  const payload: TokenPayload = { id: user.id, email: user.email, role: user.role };
  const accessToken = signAccessToken(payload);
  const refreshToken = signRefreshToken(payload);

  // Store refresh token
  await prisma.ccpRefreshToken.create({
    data: {
      token: crypto.createHash('sha256').update(refreshToken).digest('hex'),
      userId: user.id,
      expiresAt: parseExpiry(env.JWT_REFRESH_EXPIRES_IN),
    },
  });

  return {
    user: { id: user.id, email: user.email, name: user.name, role: user.role },
    accessToken,
    refreshToken,
  };
}

export async function refresh(refreshToken: string) {
  let payload: TokenPayload;
  try {
    payload = jwt.verify(refreshToken, env.JWT_REFRESH_SECRET) as TokenPayload;
  } catch {
    throw new AppError(401, 'Invalid refresh token', 'INVALID_TOKEN');
  }

  const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex');

  // Atomic rotation: delete old, create new
  const result = await prisma.$transaction(async (tx) => {
    const existing = await tx.ccpRefreshToken.findUnique({ where: { token: tokenHash } });
    if (!existing || existing.expiresAt < new Date()) {
      throw new AppError(401, 'Refresh token expired or revoked', 'TOKEN_EXPIRED');
    }

    await tx.ccpRefreshToken.delete({ where: { token: tokenHash } });

    const user = await tx.ccpUser.findUnique({ where: { id: payload.id } });
    if (!user) {
      throw new AppError(401, 'User not found', 'USER_NOT_FOUND');
    }

    const newPayload: TokenPayload = { id: user.id, email: user.email, role: user.role };
    const newAccessToken = signAccessToken(newPayload);
    const newRefreshToken = signRefreshToken(newPayload);

    await tx.ccpRefreshToken.create({
      data: {
        token: crypto.createHash('sha256').update(newRefreshToken).digest('hex'),
        userId: user.id,
        expiresAt: parseExpiry(env.JWT_REFRESH_EXPIRES_IN),
      },
    });

    return {
      user: { id: user.id, email: user.email, name: user.name, role: user.role },
      accessToken: newAccessToken,
      refreshToken: newRefreshToken,
    };
  });

  return result;
}

export async function logout(refreshToken: string) {
  const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex');
  await prisma.ccpRefreshToken.deleteMany({ where: { token: tokenHash } });
}

export async function verifyPassword(userId: string, password: string): Promise<boolean> {
  const user = await prisma.ccpUser.findUnique({ where: { id: userId } });
  if (!user) {
    throw new AppError(401, 'Authentication required', 'AUTH_REQUIRED');
  }
  return bcrypt.compare(password, user.password);
}

export async function getMe(userId: string) {
  const user = await prisma.ccpUser.findUnique({ where: { id: userId } });
  if (!user) {
    throw new AppError(401, 'Authentication required', 'AUTH_REQUIRED');
  }
  return { id: user.id, email: user.email, name: user.name, role: user.role };
}
