import { Injectable, UnauthorizedException } from '@nestjs/common';
import { createHmac, timingSafeEqual } from 'crypto';
import { UserRole, UserStatus } from '../generated/prisma/index';

export type AuthTokenPayload = {
  sub: string;
  email: string;
  name: string;
  role: UserRole;
  status: UserStatus;
  iat: number;
  exp: number;
};

@Injectable()
export class TokenService {
  private readonly secret =
    process.env.JWT_SECRET || 'erp-starline-change-this-secret';

  readonly ttlSeconds = Number(process.env.AUTH_TOKEN_TTL_SECONDS || 28800);

  sign(user: Omit<AuthTokenPayload, 'iat' | 'exp'>): string {
    const now = Math.floor(Date.now() / 1000);
    const payload: AuthTokenPayload = {
      ...user,
      iat: now,
      exp: now + this.ttlSeconds,
    };

    const header = this.encode({ alg: 'HS256', typ: 'JWT' });
    const body = this.encode(payload);
    const signature = this.signature(`${header}.${body}`);

    return `${header}.${body}.${signature}`;
  }

  verify(authorizationHeader?: string): AuthTokenPayload {
    const token = authorizationHeader?.replace(/^Bearer\s+/i, '').trim();

    if (!token) {
      throw new UnauthorizedException('Missing bearer token');
    }

    const [header, body, signature] = token.split('.');

    if (!header || !body || !signature) {
      throw new UnauthorizedException('Invalid token');
    }

    const expectedSignature = this.signature(`${header}.${body}`);
    const provided = Buffer.from(signature);
    const expected = Buffer.from(expectedSignature);

    if (
      provided.length !== expected.length ||
      !timingSafeEqual(provided, expected)
    ) {
      throw new UnauthorizedException('Invalid token signature');
    }

    let payload: AuthTokenPayload;

    try {
      payload = JSON.parse(
        Buffer.from(body, 'base64url').toString('utf8'),
      ) as AuthTokenPayload;
    } catch {
      throw new UnauthorizedException('Invalid token payload');
    }

    if (payload.exp <= Math.floor(Date.now() / 1000)) {
      throw new UnauthorizedException('Token expired');
    }

    return payload;
  }

  private encode(value: unknown): string {
    return Buffer.from(JSON.stringify(value)).toString('base64url');
  }

  private signature(value: string): string {
    return createHmac('sha256', this.secret).update(value).digest('base64url');
  }
}
