import { Injectable, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { InjectRepository } from "@nestjs/typeorm";
import { JwtService } from "@nestjs/jwt";
import * as bcrypt from "bcrypt";
import { Repository } from "typeorm";
import { AdminUser } from "./entities/admin-user.entity";

export interface AdminJwtPayload {
  sub: string;
  email: string;
}

export interface AuthenticatedAdmin {
  id: string;
  email: string;
  name: string;
}

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(AdminUser) private readonly adminUsers: Repository<AdminUser>,
    private readonly jwt: JwtService,
    private readonly config: ConfigService,
  ) {}

  async validateCredentials(email: string, password: string): Promise<AdminUser> {
    const user = await this.adminUsers.findOne({ where: { email } });
    // Same "invalid credentials" message either way — don't leak whether the
    // email exists.
    if (!user || !user.active) {
      throw new UnauthorizedException("Invalid email or password.");
    }
    const matches = await bcrypt.compare(password, user.passwordHash);
    if (!matches) {
      throw new UnauthorizedException("Invalid email or password.");
    }
    return user;
  }

  issueToken(user: AdminUser): { token: string; maxAgeMs: number } {
    const payload: AdminJwtPayload = { sub: user.id, email: user.email };
    // No expiresIn override here — JwtModule (auth.module.ts) is already
    // configured with the same JWT_EXPIRES_IN as its default signOptions.
    const expiresIn = this.config.get<string>("JWT_EXPIRES_IN") ?? "8h";
    const token = this.jwt.sign(payload);
    return { token, maxAgeMs: parseDurationToMs(expiresIn) };
  }

  async findById(id: string): Promise<AdminUser | null> {
    return this.adminUsers.findOne({ where: { id } });
  }
}

// Accepts the handful of shorthand duration formats @nestjs/jwt itself
// supports ("8h", "30m", "7d", or a bare number of seconds) so the cookie's
// Max-Age always matches the token's actual expiry instead of drifting.
function parseDurationToMs(duration: string): number {
  const match = /^(\d+)(ms|s|m|h|d)?$/.exec(duration.trim());
  if (!match) return 8 * 60 * 60 * 1000;
  const value = Number(match[1]);
  const unit = match[2] ?? "s";
  const unitMs: Record<string, number> = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
  return value * unitMs[unit];
}
