import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import type { Request } from 'express';
import { ClientLoginLog } from './client-login-log.entity';
import { getRequestMeta } from './request-meta.util';
import { parseUserAgent } from './ua-parser.util';

export interface RecordLoginInput {
  companyId?: number | null;
  clientUserId?: number | null;
  email?: string | null;
  phone?: string | null;
  method?: string; // password | otp | access_token | refresh_token | sso
  status: 'success' | 'failed';
  failureReason?: string | null;
  tokenId?: number | null;
  sessionId?: string | null;

  // Optional: pass IP / User-Agent directly when you already have them
  // (e.g. inside a service that received them as params, like verifyOtp).
  // If provided, these WIN over whatever is read from `req`.
  ipAddress?: string | null;
  userAgent?: string | null;
}

@Injectable()
export class ClientLoginLogService {
  private readonly logger = new Logger('ClientLoginLog');

  constructor(
    @InjectRepository(ClientLoginLog, 'scheme_dbs')
    private readonly repo: Repository<ClientLoginLog>,
  ) {}

  /**
   * Record a login attempt. Pass the Express `req` so we can capture device+IP.
   *
   * This is DELIBERATELY fire-and-forget and swallows its own errors:
   * an audit log must NEVER be able to break the actual login. Call it and
   * move on — do not await it in a way that can fail your auth response.
   */
  record(req: Request | null, input: RecordLoginInput): void {
    // Build the row synchronously, persist asynchronously.
    let row: ClientLoginLog;
    try {
      let meta = req
        ? getRequestMeta(req)
        : {
            ip_address: null,
            user_agent: null,
            device_type: 'unknown' as const,
            browser: null as string | null,
            browser_version: null as string | null,
            os: null as string | null,
            os_version: null as string | null,
          };

      // Explicit ip/userAgent (from the caller) override req-derived values.
      if (input.userAgent != null) {
        const parsed = parseUserAgent(input.userAgent);
        meta = {
          ...meta,
          user_agent: input.userAgent.slice(0, 1024),
          ...parsed,
        };
      }
      if (input.ipAddress != null) {
        meta = { ...meta, ip_address: input.ipAddress.slice(0, 45) };
      }

      row = this.repo.create({
        company_id: input.companyId ?? null,
        client_user_id: input.clientUserId ?? null,
        client_email: input.email ?? null,
        client_phone: input.phone ?? null,
        login_method: input.method ?? 'password',
        status: input.status,
        failure_reason: input.failureReason ?? null,
        token_id: input.tokenId ?? null,
        session_id: input.sessionId ?? null,
        ip_address: meta.ip_address,
        user_agent: meta.user_agent,
        device_type: meta.device_type,
        browser: meta.browser,
        browser_version: meta.browser_version,
        os: meta.os,
        os_version: meta.os_version,
      });
    } catch (err: any) {
      this.logger.warn(`Failed to build login-log row: ${err?.message}`);
      return;
    }

    // Do not block the request; never let a DB hiccup bubble into auth.
    this.repo.save(row).catch((err: any) => {
      this.logger.warn(`Failed to persist login-log: ${err?.message}`);
    });
  }

  /** Admin: paginated logins for one company, newest first. */
  async listForCompany(
    companyId: number,
    opts: { page?: number; limit?: number; status?: 'success' | 'failed' } = {},
  ) {
    const page = Math.max(1, Number(opts.page) || 1);
    const limit = Math.min(200, Math.max(1, Number(opts.limit) || 25));

    const where: any = { company_id: companyId };
    if (opts.status) where.status = opts.status;

    const [rows, total] = await this.repo.findAndCount({
      where,
      order: { created_at: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });

    return { rows, total, page, limit, pages: Math.ceil(total / limit) };
  }

  /** Client "my recent sessions/devices" — last N logins for one client user. */
  async listForClientUser(clientUserId: number, limit = 20) {
    return this.repo.find({
      where: { client_user_id: clientUserId },
      order: { created_at: 'DESC' },
      take: Math.min(100, Math.max(1, limit)),
    });
  }

  /** Basic brute-force visibility: failed attempts for an email in a window. */
  async recentFailuresForEmail(email: string, sinceMinutes = 15): Promise<number> {
    const since = new Date(Date.now() - sinceMinutes * 60_000);
    return this.repo
      .createQueryBuilder('l')
      .where('l.client_email = :email', { email })
      .andWhere('l.status = :s', { s: 'failed' })
      .andWhere('l.created_at >= :since', { since })
      .getCount();
  }
}
