import { randomBytes, createHash } from 'node:crypto';
import {
  BadRequestException,
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { TripInvitationsRepository } from './trip-invitations.repository';
import { TripMembersRepository } from './trip-members.repository';
import type { TripInvitation, TripMember } from './trip.types';

const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000;

function hashToken(rawToken: string): string {
  return createHash('sha256').update(rawToken).digest('hex');
}

@Injectable()
export class TripInvitationsService {
  constructor(
    private readonly invitationsRepository: TripInvitationsRepository,
    private readonly membersRepository: TripMembersRepository,
  ) {}

  async createInvitation(
    tripId: string,
    invitedByUserId: string,
    email: string,
    ttlMs = DEFAULT_TTL_MS,
  ): Promise<{ invitation: TripInvitation; rawToken: string }> {
    const rawToken = randomBytes(32).toString('base64url');
    const invitation = await this.invitationsRepository.create(tripId, {
      email,
      invitedByUserId,
      tokenHash: hashToken(rawToken),
      expiresAt: new Date(Date.now() + ttlMs),
    });
    return { invitation, rawToken };
  }

  listInvitations(tripId: string): Promise<TripInvitation[]> {
    return this.invitationsRepository.listByTrip(tripId);
  }

  removeInvitation(tripId: string, invitationId: string): Promise<void> {
    return this.invitationsRepository.remove(tripId, invitationId);
  }

  async acceptInvitation(
    rawToken: string,
    acceptingUserId: string,
  ): Promise<TripMember> {
    const invitation = await this.invitationsRepository.findByTokenHash(
      hashToken(rawToken),
    );
    if (!invitation) throw new NotFoundException('Invitation not found');
    if (invitation.acceptedAt)
      throw new ConflictException('Invitation has already been accepted');
    if (invitation.expiresAt.getTime() < Date.now())
      throw new BadRequestException('Invitation has expired');

    await this.invitationsRepository.markAccepted(invitation.id);
    return this.membersRepository.upsertActiveMember(
      invitation.tripId,
      acceptingUserId,
      'MEMBER',
    );
  }
}
