import { BadRequestException, Injectable, UnauthorizedException } from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import { JwtService } from "@nestjs/jwt";
import * as bcrypt from "bcrypt";
import { Role } from "@prisma/client";

@Injectable()
export class AuthService {
  constructor(private prisma: PrismaService, private jwt: JwtService) {}

  private async signAccessToken(userId: string) {
    const ttl = process.env.JWT_ACCESS_TTL ?? "15m";
    return this.jwt.signAsync({ sub: userId }, { secret: process.env.JWT_ACCESS_SECRET!, expiresIn: ttl });
  }

  private async signRefreshToken(userId: string) {
    const ttl = process.env.JWT_REFRESH_TTL ?? "30d";
    return this.jwt.signAsync({ sub: userId }, { secret: process.env.JWT_REFRESH_SECRET!, expiresIn: ttl });
  }

  async register(dto: { email?: string; phone?: string; password: string; displayName?: string }) {
    if (!dto.email && !dto.phone) throw new BadRequestException("Email or phone required");

    const existing = await this.prisma.user.findFirst({
      where: {
        OR: [
          dto.email ? { email: dto.email } : undefined,
          dto.phone ? { phone: dto.phone } : undefined,
        ].filter(Boolean) as any,
      },
    });
    if (existing) throw new BadRequestException("User already exists");

    const passwordHash = await bcrypt.hash(dto.password, 10);

    const user = await this.prisma.user.create({
      data: {
        email: dto.email ?? null,
        phone: dto.phone ?? null,
        passwordHash,
        status: "active",
        roles: { create: [{ role: Role.CLIENT }] },
        profile: { create: { displayName: dto.displayName ?? null } },
      },
      include: { roles: true, profile: true },
    });

    const accessToken = await this.signAccessToken(user.id);
    const refreshToken = await this.signRefreshToken(user.id);

    const refreshTokenHash = await bcrypt.hash(refreshToken, 10);
    await this.prisma.user.update({
      where: { id: user.id },
      data: { refreshTokenHash, refreshTokenUpdatedAt: new Date() },
    });

    return {
      user: { id: user.id, email: user.email, phone: user.phone, roles: user.roles.map(r => r.role) },
      accessToken,
      refreshToken,
    };
  }

  async login(dto: { email?: string; phone?: string; password: string }) {
    if (!dto.email && !dto.phone) throw new BadRequestException("Email or phone required");

    const user = await this.prisma.user.findFirst({
      where: {
        OR: [
          dto.email ? { email: dto.email } : undefined,
          dto.phone ? { phone: dto.phone } : undefined,
        ].filter(Boolean) as any,
      },
      include: { roles: true },
    });

    if (!user || !user.passwordHash) throw new UnauthorizedException("Invalid credentials");
    if (user.status !== "active") throw new UnauthorizedException("Account inactive");

    const ok = await bcrypt.compare(dto.password, user.passwordHash);
    if (!ok) throw new UnauthorizedException("Invalid credentials");

    const accessToken = await this.signAccessToken(user.id);
    const refreshToken = await this.signRefreshToken(user.id);

    const refreshTokenHash = await bcrypt.hash(refreshToken, 10);
    await this.prisma.user.update({
      where: { id: user.id },
      data: { refreshTokenHash, refreshTokenUpdatedAt: new Date() },
    });

    return {
      user: { id: user.id, email: user.email, phone: user.phone, roles: user.roles.map(r => r.role) },
      accessToken,
      refreshToken,
    };
  }

  async refresh(dto: { refreshToken: string }) {
    let payload: any;
    try {
      payload = await this.jwt.verifyAsync(dto.refreshToken, { secret: process.env.JWT_REFRESH_SECRET! });
    } catch {
      throw new UnauthorizedException("Invalid refresh token");
    }

    const userId = payload.sub as string;
    const user = await this.prisma.user.findUnique({ where: { id: userId } });
    if (!user || !user.refreshTokenHash) throw new UnauthorizedException("Invalid refresh token");

    const ok = await bcrypt.compare(dto.refreshToken, user.refreshTokenHash);
    if (!ok) throw new UnauthorizedException("Invalid refresh token");

    const accessToken = await this.signAccessToken(userId);
    const newRefreshToken = await this.signRefreshToken(userId);
    const newHash = await bcrypt.hash(newRefreshToken, 10);

    await this.prisma.user.update({
      where: { id: userId },
      data: { refreshTokenHash: newHash, refreshTokenUpdatedAt: new Date() },
    });

    return { accessToken, refreshToken: newRefreshToken };
  }

  async logout(userId: string) {
    await this.prisma.user.update({
      where: { id: userId },
      data: { refreshTokenHash: null, refreshTokenUpdatedAt: null },
    });
    return { ok: true };
  }
}
