import {
  AuditActionType,
  AuditOperationResult,
  AuditTargetType,
  type AuditActionType as AuditActionTypeCode,
  type AuditOperationResult as AuditOperationResultCode,
  type AuditTargetType as AuditTargetTypeCode,
} from "../../shared/api/enums";
import type { Id, PageResult } from "../../shared/api/types";

export const AuditGranularity = {
  DAY: "DAY",
  WEEK: "WEEK",
  MONTH: "MONTH",
} as const;
export type AuditGranularity =
  (typeof AuditGranularity)[keyof typeof AuditGranularity];

export interface AuditLogSummary {
  id: Id;
  username: string | null;
  realName: string | null;
  organizationName: string | null;
  actionType: AuditActionTypeCode;
  targetType: AuditTargetTypeCode | null;
  targetId: Id | null;
  targetName: string | null;
  operationResult: AuditOperationResultCode;
  failureReason: string | null;
  clientIp: string | null;
  requestId: string | null;
  createdAt: string;
}

export interface AuditLogQuery {
  keyword?: string;
  createdFrom?: string;
  createdTo?: string;
  userId?: Id;
  organizationId?: Id;
  actionType?: AuditActionTypeCode;
  operationResult?: AuditOperationResultCode;
  targetType?: AuditTargetTypeCode;
  targetId?: Id;
  page?: number;
  pageSize?: number;
  sortDirection?: "asc" | "desc";
}

export interface AuditTimeRange {
  createdFrom?: string;
  createdTo?: string;
}

export interface AuditTrendQuery extends AuditTimeRange {
  granularity: AuditGranularity;
}

export interface AuditUserStatisticsQuery extends AuditTimeRange {
  limit?: number;
}

export interface AuditTrendItem {
  period: string;
  operationCount: number;
}

export interface AuditActionStatisticsItem {
  actionType: AuditActionTypeCode;
  count: number;
}

export interface AuditUserStatisticsItem {
  userId: Id;
  realName: string | null;
  organizationName: string | null;
  operationCount: number;
}

type RecordValue = Record<string, unknown>;
const ID_PATTERN = /^[0-9]+$/;
const UTC_PATTERN =
  /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;

function isRecord(value: unknown): value is RecordValue {
  return !!value && typeof value === "object" && !Array.isArray(value);
}

function hasExactKeys(value: RecordValue, keys: readonly string[]): boolean {
  const actual = Object.keys(value).sort();
  const expected = [...keys].sort();
  return (
    actual.length === expected.length &&
    actual.every((key, index) => key === expected[index])
  );
}

function isStringId(value: unknown): value is Id {
  return typeof value === "string" && ID_PATTERN.test(value);
}

function isNullableString(value: unknown): value is string | null {
  return value === null || typeof value === "string";
}

function isNonNegativeInteger(value: unknown): value is number {
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
}

function isEnum<T extends Record<string, string>>(
  values: T,
  value: unknown,
): boolean {
  return (
    typeof value === "string" &&
    (Object.values(values) as string[]).includes(value)
  );
}

export function isUtcDateTime(value: unknown): value is string {
  return typeof value === "string" && UTC_PATTERN.test(value);
}

export function isAuditLogSummary(
  value: unknown,
): value is AuditLogSummary {
  if (
    !isRecord(value) ||
    !hasExactKeys(value, [
      "id",
      "username",
      "realName",
      "organizationName",
      "actionType",
      "targetType",
      "targetId",
      "targetName",
      "operationResult",
      "failureReason",
      "clientIp",
      "requestId",
      "createdAt",
    ])
  ) {
    return false;
  }
  return (
    isStringId(value.id) &&
    isNullableString(value.username) &&
    isNullableString(value.realName) &&
    isNullableString(value.organizationName) &&
    isEnum(AuditActionType, value.actionType) &&
    (value.targetType === null ||
      isEnum(AuditTargetType, value.targetType)) &&
    (value.targetId === null || isStringId(value.targetId)) &&
    isNullableString(value.targetName) &&
    isEnum(AuditOperationResult, value.operationResult) &&
    isNullableString(value.failureReason) &&
    isNullableString(value.clientIp) &&
    isNullableString(value.requestId) &&
    isUtcDateTime(value.createdAt)
  );
}

export function isAuditLogPage(
  value: unknown,
): value is PageResult<AuditLogSummary> {
  if (
    !isRecord(value) ||
    !hasExactKeys(value, [
      "items",
      "page",
      "pageSize",
      "total",
      "totalPages",
    ])
  ) {
    return false;
  }
  return (
    Array.isArray(value.items) &&
    value.items.every(isAuditLogSummary) &&
    isNonNegativeInteger(value.page) &&
    value.page >= 1 &&
    isNonNegativeInteger(value.pageSize) &&
    value.pageSize >= 1 &&
    value.pageSize <= 100 &&
    isNonNegativeInteger(value.total) &&
    isNonNegativeInteger(value.totalPages)
  );
}

export function isAuditTrendData(
  value: unknown,
): value is { items: AuditTrendItem[] } {
  if (!isRecord(value) || !hasExactKeys(value, ["items"])) return false;
  return (
    Array.isArray(value.items) &&
    value.items.every(
      (item) =>
        isRecord(item) &&
        hasExactKeys(item, ["period", "operationCount"]) &&
        typeof item.period === "string" &&
        isNonNegativeInteger(item.operationCount),
    )
  );
}

export function isAuditActionStatisticsData(
  value: unknown,
): value is { items: AuditActionStatisticsItem[] } {
  if (!isRecord(value) || !hasExactKeys(value, ["items"])) return false;
  return (
    Array.isArray(value.items) &&
    value.items.every(
      (item) =>
        isRecord(item) &&
        hasExactKeys(item, ["actionType", "count"]) &&
        isEnum(AuditActionType, item.actionType) &&
        isNonNegativeInteger(item.count),
    )
  );
}

export function isAuditUserStatisticsData(
  value: unknown,
): value is { items: AuditUserStatisticsItem[] } {
  if (!isRecord(value) || !hasExactKeys(value, ["items"])) return false;
  return (
    Array.isArray(value.items) &&
    value.items.every(
      (item) =>
        isRecord(item) &&
        hasExactKeys(item, [
          "userId",
          "realName",
          "organizationName",
          "operationCount",
        ]) &&
        isStringId(item.userId) &&
        isNullableString(item.realName) &&
        isNullableString(item.organizationName) &&
        isNonNegativeInteger(item.operationCount),
    )
  );
}

