import { Transform, Type } from 'class-transformer';
import {
  ArrayMaxSize,
  IsArray,
  IsIn,
  IsInt,
  IsOptional,
  IsString,
  Matches,
  Max,
  MaxLength,
  Min,
} from 'class-validator';
import {
  ACTIVITY_ACTIONS,
  ACTIVITY_OUTCOMES,
} from '@/common/decorators/activity-log.decorator';

// The filter panel sends several choices as one comma-separated value
// ("auth,users"); this turns it into a list. A single value still works.
const toList = ({ value }: { value: unknown }) =>
  typeof value === 'string'
    ? value
        .split(',')
        .map((item) => item.trim())
        .filter(Boolean)
    : value;

// The columns the Activity Log table can be sorted by. Each one is a real,
// indexed column, so sorting stays fast on a large log.
export const ACTIVITY_SORT_FIELDS = [
  'createdAt',
  'actorName',
  'module',
  'action',
  'recordReference',
] as const;
export type ActivitySortField = (typeof ACTIVITY_SORT_FIELDS)[number];

export class ActivityLogQueryDto {
  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(1000000)
  page = 1;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(100)
  pageSize = 25;

  @IsOptional()
  @IsString()
  @MaxLength(200)
  search?: string;

  // Each of these may hold several choices; the log matches any of them.
  @IsOptional()
  @Transform(toList)
  @IsArray()
  @ArrayMaxSize(50)
  @IsString({ each: true })
  @MaxLength(80, { each: true })
  module?: string[];

  @IsOptional()
  @Transform(toList)
  @IsArray()
  @ArrayMaxSize(50)
  @IsIn(ACTIVITY_ACTIONS, { each: true })
  action?: string[];

  @IsOptional()
  @Transform((params) => {
    const list = toList(params);
    return Array.isArray(list) ? list.map(Number) : list;
  })
  @IsArray()
  @ArrayMaxSize(50)
  @IsInt({ each: true })
  @Min(1, { each: true })
  @Max(2147483647, { each: true })
  actorId?: number[];

  @IsOptional()
  @Matches(/^\d{4}-\d{2}-\d{2}$/)
  from?: string;

  @IsOptional()
  @Matches(/^\d{4}-\d{2}-\d{2}$/)
  to?: string;

  // The actor's role(s) at the time of the action; several allowed.
  @IsOptional()
  @Transform(toList)
  @IsArray()
  @ArrayMaxSize(50)
  @IsString({ each: true })
  @MaxLength(80, { each: true })
  role?: string[];

  // One exact record reference, for "View full history" of that record.
  @IsOptional()
  @IsString()
  @MaxLength(255)
  reference?: string;

  // How the actions ended (SUCCESS, DENIED, FAILED); several allowed.
  @IsOptional()
  @Transform(toList)
  @IsArray()
  @ArrayMaxSize(3)
  @IsIn(ACTIVITY_OUTCOMES, { each: true })
  outcome?: string[];

  // One record's history: its id, combined with the module filter.
  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(2147483647)
  recordId?: number;

  // Column to sort by. Left out, the list stays newest first.
  @IsOptional()
  @IsIn(ACTIVITY_SORT_FIELDS)
  sortBy?: ActivitySortField;

  @IsOptional()
  @IsIn(['asc', 'desc'])
  sortOrder?: 'asc' | 'desc';
}
