import { randomUUID } from 'node:crypto';
import type { PrismaClient } from '@prisma/client';
import bcrypt from 'bcrypt';

/** 30 days in milliseconds */
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;

export interface LoginResult {
  token: string;
  expiresAt: Date;
  user: { id: string; email: string; role: string };
}

export class AuthenticationError extends Error {
  readonly statusCode = 401;
  constructor(message: string) {
    super(message);
    this.name = 'AuthenticationError';
  }
}

export class AuthService {
  constructor(private readonly prisma: PrismaClient) {}

  async login(email: string, password: string): Promise<LoginResult> {
    const user = await this.prisma.user.findUnique({ where: { email } });
    if (user === null) {
      throw new AuthenticationError('Invalid email or password');
    }

    const valid = await bcrypt.compare(password, user.passwordHash);
    if (!valid) {
      throw new AuthenticationError('Invalid email or password');
    }

    const token = randomUUID();
    const expiresAt = new Date(Date.now() + SESSION_TTL_MS);

    await this.prisma.session.create({
      data: {
        token,
        userId: user.id,
        expiresAt,
      },
    });

    return {
      token,
      expiresAt,
      user: { id: user.id, email: user.email, role: user.role },
    };
  }

  async logout(token: string): Promise<void> {
    // Delete the session by token; ignore if already deleted
    await this.prisma.session.deleteMany({ where: { token } });
  }

  async findSession(token: string): Promise<{ userId: string; expiresAt: Date } | null> {
    const session = await this.prisma.session.findUnique({ where: { token } });
    if (session === null) {
      return null;
    }
    return { userId: session.userId, expiresAt: session.expiresAt };
  }

  /**
   * Create a session for a user by email without requiring their password.
   * Used for admin impersonation.
   */
  async impersonate(email: string): Promise<LoginResult> {
    const user = await this.prisma.user.findUnique({ where: { email } });
    if (user === null) {
      throw new AuthenticationError('User not found');
    }

    const token = randomUUID();
    const expiresAt = new Date(Date.now() + SESSION_TTL_MS);

    await this.prisma.session.create({
      data: {
        token,
        userId: user.id,
        expiresAt,
      },
    });

    return {
      token,
      expiresAt,
      user: { id: user.id, email: user.email, role: user.role },
    };
  }
}
