import { Injectable } from '@nestjs/common';
import type { Request } from 'express';
import { AuditLogRepository } from '../repositories/audit-log.repository';
import {
  MeetingAttendanceAuditAction,
  MeetingAttendanceAuditResource,
} from '../constants/audit';
import { getMeetingRoleFromUser, normalizeMeetingRole } from '../utils/meeting-roles';

type ActorLike =
  | {
      userId?: string;
      id?: string;
      email?: string;
      displayName?: string;
      username?: string;
      role?: string;
      roles?: Array<{ role?: { code?: string } } | string>;
    }
  | undefined
  | null;

@Injectable()
export class MeetingAttendanceAuditLogWriter {
  constructor(private readonly auditLogRepository: AuditLogRepository) {}

  async log(params: {
    request?: Request;
    actor?: ActorLike;
    action: MeetingAttendanceAuditAction;
    resource: MeetingAttendanceAuditResource;
    statusCode: number;
    resourceId?: string;
    requestBody?: Record<string, unknown>;
    changes?: Record<string, unknown>;
    errorMessage?: string;
    duration?: number;
    source?: string;
  }) {
    const userId = params.actor?.userId ?? params.actor?.id;
    if (!userId) {
      return;
    }

    const userEmail = params.actor?.email || 'unknown';
    const userName =
      params.actor?.displayName || params.actor?.username || params.actor?.email || 'Unknown';

    const roleFromRoles = getMeetingRoleFromUser(params.actor ?? null);
    const roleFromField = normalizeMeetingRole(params.actor?.role);
    const userRole = roleFromRoles || roleFromField || 'Employee';

    const request = params.request;
    const endpoint = request?.originalUrl || request?.url || '';
    const method = request?.method || 'N/A';
    const userAgent = this.getHeader(request, 'user-agent');
    const deviceId = this.getHeader(request, 'x-device-id');
    const sessionId = this.getHeader(request, 'x-session-id');
    const requestId = this.getHeader(request, 'x-request-id') || (request as any)?.id;
    const traceId = this.getHeader(request, 'x-trace-id');
    const geoLocation = this.getHeader(request, 'x-geo-location');
    const ipAddress = this.getClientIp(request);

    const payload = {
      userId,
      userEmail,
      userName,
      userRole,
      action: params.action,
      resource: params.resource,
      resourceId: params.resourceId,
      method,
      endpoint,
      statusCode: params.statusCode,
      source: params.source || 'WEB',
      deviceId,
      sessionId,
      requestId,
      traceId,
      geoLocation,
      ipAddress,
      userAgent,
      requestBody: this.toJson(params.requestBody),
      changes: this.toJson(params.changes),
      errorMessage: params.errorMessage,
      duration: params.duration,
    };

    try {
      await this.auditLogRepository.createAuditLog(payload);
    } catch {
      // 审计日志失败不应影响主流程
    }
  }

  private toJson(value?: Record<string, unknown>) {
    if (!value) return undefined;
    try {
      return JSON.stringify(value);
    } catch {
      return undefined;
    }
  }

  private getHeader(request: Request | undefined, name: string) {
    if (!request) return undefined;
    const value = request.headers[name.toLowerCase()];
    if (Array.isArray(value)) return value[0];
    return value;
  }

  private getClientIp(request: Request | undefined) {
    if (!request) return undefined;
    const forwarded = this.getHeader(request, 'x-forwarded-for');
    if (forwarded) {
      return forwarded.split(',')[0]?.trim();
    }
    return request.ip;
  }
}
