import {
  Injectable,
  UnauthorizedException,
  BadRequestException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcryptjs';
import type { AuthUser, LoginInput, ChangePasswordInput } from '@vihar/shared';
import { PrismaService } from '../../common/prisma/prisma.service';

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

  // Dummy hash used so bcrypt.compare always runs, preventing username enumeration via timing
  private static readonly DUMMY_HASH =
    '$2a$11$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';

  async login(input: LoginInput): Promise<{ token: string; user: AuthUser }> {
    // Phase 1: single tenant means username is unique enough; in multi-city,
    // we'd need a city selector or subdomain-based routing. For now we look
    // up across all cities and require username to be globally unique in pilot.
    const user = await this.prisma.user.findFirst({
      where: { username: input.username, isActive: true },
    });

    // Always run bcrypt to prevent timing-based username enumeration
    const ok = await bcrypt.compare(input.password, user?.passwordHash ?? AuthService.DUMMY_HASH);
    if (!user || !ok) throw new UnauthorizedException('Invalid credentials');

    // Update last login (fire and forget)
    this.prisma.user
      .update({ where: { userId: user.userId }, data: { lastLoginAt: new Date() } })
      .catch(() => {
        /* non-critical */
      });

    const authUser: AuthUser = {
      userId: user.userId,
      cityId: user.cityId,
      username: user.username,
      fullName: user.fullName,
      phone: user.phone,
      homeLocalityId: user.homeLocalityId,
      isCaptain: user.isCaptain,
      isVolunteer: user.isVolunteer,
      isSuperAdmin: user.isSuperAdmin,
    };

    const token = await this.jwt.signAsync(authUser);
    return { token, user: authUser };
  }

  async getMe(userId: number): Promise<AuthUser> {
    const user = await this.prisma.user.findUnique({ where: { userId } });
    if (!user || !user.isActive) throw new UnauthorizedException('User not found or inactive');
    return {
      userId: user.userId,
      cityId: user.cityId,
      username: user.username,
      fullName: user.fullName,
      phone: user.phone,
      homeLocalityId: user.homeLocalityId,
      isCaptain: user.isCaptain,
      isVolunteer: user.isVolunteer,
      isSuperAdmin: user.isSuperAdmin,
    };
  }

  async changePassword(userId: number, input: ChangePasswordInput): Promise<void> {
    const user = await this.prisma.user.findUnique({ where: { userId } });
    if (!user) throw new UnauthorizedException('User not found');

    const ok = await bcrypt.compare(input.oldPassword, user.passwordHash);
    if (!ok) throw new BadRequestException('Old password is incorrect');

    const newHash = await bcrypt.hash(input.newPassword, 11);
    await this.prisma.user.update({
      where: { userId },
      data: { passwordHash: newHash },
    });
  }

  cookieOptions() {
    const expiresIn = this.config.get<string>('JWT_EXPIRES_IN') || '7d';
    // Convert "7d" to ms (rough; only handles d/h)
    const match = expiresIn.match(/^(\d+)([dh])$/);
    let maxAgeMs = 7 * 24 * 60 * 60 * 1000;
    if (match) {
      const n = parseInt(match[1]!, 10);
      const unit = match[2]!;
      maxAgeMs = unit === 'd' ? n * 86400_000 : n * 3600_000;
    }
    return {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict' as const,
      maxAge: maxAgeMs,
      path: '/',
    };
  }
}
