import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { User } from '@prisma/client';
import * as bcrypt from 'bcrypt';
import { createHash } from 'crypto';
import ms = require('ms');
import type { StringValue } from 'ms';
import { PrismaService } from '../prisma/prisma.service';
import { UsersService } from '../users/users.service';
import { RefreshDto, SigninDto, SignupDto } from './dto';

@Injectable()
export class AuthService {
  constructor(
    private readonly users: UsersService,
    private readonly prisma: PrismaService,
    private readonly jwt: JwtService,
    private readonly config: ConfigService,
  ) {}

  async signup(dto: SignupDto) {
    const user = await this.users.create({
      name: dto.name,
      email: dto.email.toLowerCase(),
      phone: dto.phone,
      passwordHash: await bcrypt.hash(dto.password, 12),
    });
    return this.issueTokens(user);
  }

  async signin(dto: SigninDto) {
    const user = await this.users.findActiveByEmail(dto.email);
    if (!user || !(await bcrypt.compare(dto.password, user.passwordHash))) {
      throw new UnauthorizedException('Invalid email or password');
    }
    if (user.status !== 'ACTIVE') throw new UnauthorizedException('User is inactive');
    return this.issueTokens(user);
  }

  async refresh(dto: RefreshDto) {
    const tokenHash = await this.hashToken(dto.refreshToken);
    const session = await this.prisma.refreshToken.findFirst({
      where: { tokenHash, revokedAt: null, expiresAt: { gt: new Date() }, user: { deletedAt: null, status: 'ACTIVE' } },
      include: { user: true },
    });
    if (!session) throw new UnauthorizedException('Invalid refresh token');
    await this.prisma.refreshToken.update({ where: { id: session.id }, data: { revokedAt: new Date() } });
    return this.issueTokens(session.user);
  }

  async logout(userId: string, dto?: RefreshDto) {
    if (dto?.refreshToken) {
      await this.prisma.refreshToken.updateMany({
        where: { userId, tokenHash: await this.hashToken(dto.refreshToken), revokedAt: null },
        data: { revokedAt: new Date() },
      });
    } else {
      await this.prisma.refreshToken.updateMany({ where: { userId, revokedAt: null }, data: { revokedAt: new Date() } });
    }
    return { message: 'Logged out successfully', data: null };
  }

  private async issueTokens(user: User) {
    const payload = { sub: user.id, email: user.email, role: user.role };
    const accessExpiresIn = this.config.get<string>('JWT_ACCESS_EXPIRES_IN', '15m') as StringValue;
    const refreshExpiresIn = this.config.get<string>('JWT_REFRESH_EXPIRES_IN', '7d') as StringValue;
    const accessToken = await this.jwt.signAsync(payload, {
      secret: this.config.getOrThrow<string>('JWT_ACCESS_SECRET'),
      expiresIn: accessExpiresIn,
    });
    const refreshToken = await this.jwt.signAsync(payload, {
      secret: this.config.getOrThrow<string>('JWT_REFRESH_SECRET'),
      expiresIn: refreshExpiresIn,
    });
    await this.prisma.refreshToken.create({
      data: {
        userId: user.id,
        tokenHash: await this.hashToken(refreshToken),
        expiresAt: new Date(Date.now() + ms(refreshExpiresIn)),
      },
    });
    return {
      message: 'Authentication successful',
      data: { accessToken, refreshToken, user: this.users.toSafeUser(user) },
    };
  }

  private async hashToken(token: string) {
    return createHash('sha256').update(token).digest('hex');
  }
}
