import {
  Injectable,
  BadRequestException,
  UnauthorizedException,
  ConflictException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcryptjs';
import { v4 as uuidv4 } from 'uuid';
import { PrismaService } from '../../prisma/prisma.service';
import { RegisterDto, LoginDto, SendOtpDto, VerifyOtpDto } from './dto/register.dto';

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

  async register(dto: RegisterDto) {
    if (!dto.phone && !dto.email) {
      throw new BadRequestException('Phone or email is required');
    }

    const existing = await this.prisma.user.findFirst({
      where: {
        OR: [
          dto.phone ? { phone: dto.phone } : {},
          dto.email ? { email: dto.email } : {},
        ].filter((c) => Object.keys(c).length > 0),
      },
    });
    if (existing) throw new ConflictException('User already exists');

    const passwordHash = dto.password ? await bcrypt.hash(dto.password, 12) : null;

    const user = await this.prisma.user.create({
      data: {
        name: dto.name,
        phone: dto.phone,
        email: dto.email,
        passwordHash,
        status: 'PENDING',
        membership: { create: { type: 'FREE' } },
      },
    });

    return { message: 'Registration successful. Awaiting admin approval.', userId: user.id };
  }

  async sendOtp(dto: SendOtpDto) {
    const user = await this.prisma.user.findUnique({ where: { phone: dto.phone } });
    if (!user) throw new BadRequestException('No account found with this phone number');

    const code = Math.floor(100000 + Math.random() * 900000).toString();
    const expiresAt = new Date(Date.now() + 10 * 60 * 1000);

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

    // TODO: send via MSG91
    console.log(`OTP for ${dto.phone}: ${code}`);

    return { message: 'OTP sent successfully' };
  }

  async verifyOtp(dto: VerifyOtpDto) {
    const user = await this.prisma.user.findUnique({ where: { phone: dto.phone } });
    if (!user) throw new UnauthorizedException('Invalid credentials');

    if (user.status !== 'APPROVED') {
      throw new UnauthorizedException('Account pending approval');
    }

    const otp = await this.prisma.oTP.findFirst({
      where: {
        userId: user.id,
        code: dto.code,
        used: false,
        expiresAt: { gt: new Date() },
      },
      orderBy: { createdAt: 'desc' },
    });
    if (!otp) throw new UnauthorizedException('Invalid or expired OTP');

    await this.prisma.oTP.update({ where: { id: otp.id }, data: { used: true } });

    return this.issueTokens(user.id);
  }

  async loginWithEmail(dto: LoginDto) {
    const user = await this.prisma.user.findUnique({ where: { email: dto.email } });
    if (!user?.passwordHash) throw new UnauthorizedException('Invalid credentials');
    if (user.status !== 'APPROVED') throw new UnauthorizedException('Account pending approval');

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

    return this.issueTokens(user.id);
  }

  async refreshToken(token: string) {
    const stored = await this.prisma.refreshToken.findUnique({ where: { token } });
    if (!stored || stored.expiresAt < new Date()) {
      throw new UnauthorizedException('Invalid refresh token');
    }
    await this.prisma.refreshToken.delete({ where: { id: stored.id } });
    return this.issueTokens(stored.userId);
  }

  async logout(userId: string) {
    await this.prisma.refreshToken.deleteMany({ where: { userId } });
    return { message: 'Logged out successfully' };
  }

  private async issueTokens(userId: string) {
    const accessToken = this.jwt.sign({ sub: userId });

    // Parse JWT_REFRESH_EXPIRES_IN (e.g. "90d", "30d") into milliseconds
    const refreshExpiresIn = this.config.get<string>('JWT_REFRESH_EXPIRES_IN', '90d');
    const refreshMs = this.parseDuration(refreshExpiresIn);

    const refreshToken = uuidv4();
    const expiresAt = new Date(Date.now() + refreshMs);

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

    return { accessToken, refreshToken };
  }

  /** Convert a duration string like "7d", "90d", "24h" to milliseconds. */
  private parseDuration(str: string): number {
    const n = parseInt(str, 10);
    if (str.endsWith('d')) return n * 24 * 60 * 60 * 1000;
    if (str.endsWith('h')) return n * 60 * 60 * 1000;
    if (str.endsWith('m')) return n * 60 * 1000;
    return 90 * 24 * 60 * 60 * 1000; // fallback: 90 days
  }
}
