/**
 * AuditLog Service
 * General activity tracking and audit log management.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import {
  AuditLog,
  AuditAction,
  AuditCategory,
  AuditStatus,
} from '../entities/audit-log.entity';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateAuditLogDto {
  userId?: string;
  userEmail?: string;
  userName?: string;
  sessionId?: string;
  impersonatorId?: string;
  action: AuditAction;
  actionCategory?: AuditCategory;
  resourceType: string;
  resourceId?: string;
  resourceName?: string;
  oldValues?: Record<string, any>;
  newValues?: Record<string, any>;
  changedFields?: string[];
  ipAddress?: string;
  userAgent?: string;
  deviceInfo?: Record<string, any>;
  location?: Record<string, any>;
  requestId?: string;
  requestMethod?: string;
  requestPath?: string;
  requestParams?: Record<string, any>;
  status?: AuditStatus;
  errorMessage?: string;
  durationMs?: number;
  metadata?: Record<string, any>;
  tags?: string[];
}

export interface AuditLogFilters {
  userId?: string;
  action?: AuditAction;
  actionCategory?: AuditCategory;
  resourceType?: string;
  resourceId?: string;
  status?: AuditStatus;
  ipAddress?: string;
  dateFrom?: Date;
  dateTo?: Date;
  search?: string;
  tags?: string[];
}

export interface AuditLogStats {
  period: { days: number; from: Date; to: Date };
  total: number;
  byAction: { action: string; count: number }[];
  byCategory: { category: string; count: number }[];
  byStatus: { status: string; count: number }[];
  byResourceType: { resourceType: string; count: number }[];
  successRate: number;
}

export class AuditLogService {
  constructor(private readonly repository: Repository<AuditLog>) {}

  /**
   * Create a new audit log entry
   */
  async create(
    ctx: ServiceContext,
    dto: CreateAuditLogDto,
  ): Promise<AuditLog> {
    const log = this.repository.create({
      tenantId: ctx.tenantId,
      userId: dto.userId || ctx.userId,
      userEmail: dto.userEmail,
      userName: dto.userName,
      sessionId: dto.sessionId,
      impersonatorId: dto.impersonatorId,
      action: dto.action,
      actionCategory: dto.actionCategory || 'data',
      resourceType: dto.resourceType,
      resourceId: dto.resourceId,
      resourceName: dto.resourceName,
      oldValues: dto.oldValues,
      newValues: dto.newValues,
      changedFields: dto.changedFields,
      ipAddress: dto.ipAddress,
      userAgent: dto.userAgent,
      deviceInfo: dto.deviceInfo || {},
      location: dto.location || {},
      requestId: dto.requestId,
      requestMethod: dto.requestMethod,
      requestPath: dto.requestPath,
      requestParams: dto.requestParams || {},
      status: dto.status || 'success',
      errorMessage: dto.errorMessage,
      durationMs: dto.durationMs,
      metadata: dto.metadata || {},
      tags: dto.tags || [],
    });

    return this.repository.save(log);
  }

  /**
   * Find audit logs with filters and pagination
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: AuditLogFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<AuditLog>> {
    const qb = this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('al.user_id = :userId', { userId: filters.userId });
    }
    if (filters.action) {
      qb.andWhere('al.action = :action', { action: filters.action });
    }
    if (filters.actionCategory) {
      qb.andWhere('al.action_category = :category', { category: filters.actionCategory });
    }
    if (filters.resourceType) {
      qb.andWhere('al.resource_type = :resourceType', { resourceType: filters.resourceType });
    }
    if (filters.resourceId) {
      qb.andWhere('al.resource_id = :resourceId', { resourceId: filters.resourceId });
    }
    if (filters.status) {
      qb.andWhere('al.status = :status', { status: filters.status });
    }
    if (filters.ipAddress) {
      qb.andWhere('al.ip_address = :ipAddress', { ipAddress: filters.ipAddress });
    }
    if (filters.dateFrom) {
      qb.andWhere('al.created_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('al.created_at <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.search) {
      qb.andWhere(
        '(al.resource_name ILIKE :search OR al.user_name ILIKE :search OR al.user_email ILIKE :search)',
        { search: `%${filters.search}%` },
      );
    }
    if (filters.tags && filters.tags.length > 0) {
      qb.andWhere('al.tags && :tags', { tags: filters.tags });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('al.created_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Find audit log by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<AuditLog | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
  }

  /**
   * Find logs by resource (entity)
   */
  async findByResource(
    ctx: ServiceContext,
    resourceType: string,
    resourceId: string,
    page = 1,
    limit = 20,
  ): Promise<PaginatedResult<AuditLog>> {
    return this.findWithFilters(ctx, { resourceType, resourceId }, page, limit);
  }

  /**
   * Find logs by user
   */
  async findByUser(
    ctx: ServiceContext,
    userId: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<AuditLog>> {
    return this.findWithFilters(ctx, { userId }, page, limit);
  }

  /**
   * Get recent activity for a resource
   */
  async getRecentActivity(
    ctx: ServiceContext,
    resourceType: string,
    resourceId: string,
    limit = 10,
  ): Promise<AuditLog[]> {
    return this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.resource_type = :resourceType', { resourceType })
      .andWhere('al.resource_id = :resourceId', { resourceId })
      .orderBy('al.created_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get failed operations
   */
  async getFailedOperations(
    ctx: ServiceContext,
    hours = 24,
    limit = 100,
  ): Promise<AuditLog[]> {
    const dateFrom = new Date();
    dateFrom.setHours(dateFrom.getHours() - hours);

    return this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.status = :status', { status: 'failure' })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .orderBy('al.created_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get statistics for audit logs
   */
  async getStats(ctx: ServiceContext, days = 30): Promise<AuditLogStats> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const total = await this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .getCount();

    const byAction = await this.repository
      .createQueryBuilder('al')
      .select('al.action', 'action')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.action')
      .getRawMany();

    const byCategory = await this.repository
      .createQueryBuilder('al')
      .select('al.action_category', 'category')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.action_category')
      .getRawMany();

    const byStatus = await this.repository
      .createQueryBuilder('al')
      .select('al.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.status')
      .getRawMany();

    const byResourceType = await this.repository
      .createQueryBuilder('al')
      .select('al.resource_type', 'resourceType')
      .addSelect('COUNT(*)', 'count')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .groupBy('al.resource_type')
      .orderBy('count', 'DESC')
      .limit(10)
      .getRawMany();

    const successCount = await this.repository
      .createQueryBuilder('al')
      .where('al.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('al.created_at >= :dateFrom', { dateFrom })
      .andWhere('al.status = :status', { status: 'success' })
      .getCount();

    return {
      period: { days, from: dateFrom, to: new Date() },
      total,
      byAction: byAction.map((r) => ({ action: r.action, count: parseInt(r.count) })),
      byCategory: byCategory.map((r) => ({ category: r.category, count: parseInt(r.count) })),
      byStatus: byStatus.map((r) => ({ status: r.status, count: parseInt(r.count) })),
      byResourceType: byResourceType.map((r) => ({ resourceType: r.resourceType, count: parseInt(r.count) })),
      successRate: total > 0 ? (successCount / total) * 100 : 0,
    };
  }
}
