import bcrypt from 'bcryptjs';
import { prisma } from '../config/database';
import { userRepository } from '../repositories/userRepository';
import { profileRepository } from '../repositories/profileRepository';
import { activityRepository } from '../repositories/activityRepository';
import {
  signAccessToken,
  signRefreshToken,
  TokenPayload,
} from '../utils/jwt';
import { AppError, randomToken, toISO } from '../utils/helpers';
import { Role, Jurusan } from '@prisma/client';

export interface AuthTokens {
  accessToken: string;
  refreshToken: string;
}

export interface RegisterInput {
  fullName: string;
  email: string;
  password: string;
  phone?: string;
  address?: string;
  school?: string;
  jurusan?: Jurusan;
}

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

export const authService = {
  async login(email: string, password: string): Promise<{
    tokens: AuthTokens;
    user: TokenPayload;
  }> {
    const user = await userRepository.findByEmail(email.toLowerCase());
    if (!user) {
      throw new AppError('Invalid email or password', 401);
    }
    const valid = await bcrypt.compare(password, user.password);
    if (!valid) {
      throw new AppError('Invalid email or password', 401);
    }
    if (!user.isActive) {
      throw new AppError('Your account has been deactivated', 403);
    }

    const payload: TokenPayload = {
      userId: user.id,
      email: user.email,
      role: user.role,
      nim: user.nim,
    };

    await activityRepository.log({
      userId: user.id,
      description: 'User logged in',
      status: 'login',
    });

    return {
      tokens: {
        accessToken: signAccessToken(payload),
        refreshToken: signRefreshToken(payload),
      },
      user: payload,
    };
  },

  async register(input: RegisterInput) {
    const existing = await userRepository.findByEmail(input.email.toLowerCase());
    if (existing) {
      throw new AppError('Email is already registered', 409);
    }

    const hashed = await hashPassword(input.password);
    const user = await prisma.$transaction(async (tx) => {
      const created = await tx.user.create({
        data: {
          email: input.email.toLowerCase(),
          password: hashed,
          role: 'student' as Role,
        },
      });
      await tx.profile.create({
        data: {
          userId: created.id,
          fullName: input.fullName,
          phoneNumber: input.phone ?? null,
          address: input.address ?? null,
          asalSekolah: input.school ?? null,
          jurusan: input.jurusan ?? null,
        },
      });
      await tx.registrationStatus.create({
        data: { userId: created.id, status: 'draft', lastStepCompleted: 0 },
      });
      return created;
    });

    await activityRepository.log({
      userId: user.id,
      description: 'New student registered',
      status: 'register',
    });

    const payload: TokenPayload = {
      userId: user.id,
      email: user.email,
      role: user.role,
      nim: user.nim,
    };

    return {
      tokens: {
        accessToken: signAccessToken(payload),
        refreshToken: signRefreshToken(payload),
      },
      user: payload,
    };
  },

  async forgotPassword(email: string): Promise<{ token: string }> {
    const user = await userRepository.findByEmail(email.toLowerCase());
    if (!user) {
      throw new AppError('Email is not registered', 404);
    }
    const token = randomToken(40);
    const expiresAt = new Date(Date.now() + 30 * 60 * 1000);
    await prisma.passwordReset.create({
      data: { email: user.email, token, expiresAt, used: false },
    });
    await activityRepository.log({
      userId: user.id,
      description: 'Password reset requested',
      status: 'reset',
    });
    return { token };
  },

  async resetPassword(token: string, password: string): Promise<void> {
    const record = await prisma.passwordReset.findFirst({
      where: { token, used: false },
    });
    if (!record) {
      throw new AppError('Invalid or used reset token', 400);
    }
    if (record.expiresAt.getTime() < Date.now()) {
      throw new AppError('Reset token has expired', 400);
    }
    const user = await userRepository.findByEmail(record.email);
    if (!user) {
      throw new AppError('User not found', 404);
    }
    const hashed = await hashPassword(password);
    await prisma.$transaction([
      prisma.user.update({ where: { id: user.id }, data: { password: hashed } }),
      prisma.passwordReset.update({
        where: { id: record.id },
        data: { used: true },
      }),
    ]);
    await activityRepository.log({
      userId: user.id,
      description: 'Password reset completed',
      status: 'reset',
    });
  },

  async getMe(userId: number) {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      include: {
        profile: true,
        registrationStatus: true,
        documents: true,
        payments: { orderBy: { createdAt: 'desc' } },
        notifications: { where: { isRead: false }, orderBy: { createdAt: 'desc' } },
      },
    });
    if (!user) {
      throw new AppError('User not found', 404);
    }
    return {
      id: user.id,
      email: user.email,
      role: user.role,
      nim: user.nim,
      isActive: user.isActive,
      createdAt: toISO(user.createdAt),
      updatedAt: toISO(user.updatedAt),
      profile: user.profile
        ? {
            id: user.profile.id,
            userId: user.profile.userId,
            fullName: user.profile.fullName,
            phoneNumber: user.profile.phoneNumber,
            address: user.profile.address,
            asalSekolah: user.profile.asalSekolah,
            jurusan: user.profile.jurusan,
            statusPendaftaran: user.profile.statusPendaftaran,
            angkatan: user.profile.angkatan,
            createdAt: toISO(user.profile.createdAt),
            updatedAt: toISO(user.profile.updatedAt),
          }
        : null,
      registrationStatus: user.registrationStatus,
      documents: user.documents,
      payments: user.payments,
      notifications: user.notifications,
    };
  },
};
