export const USER_ROLES = [
  'SUPER_ADMIN', 'NATIONAL_ADMINISTRATOR', 'IT_TECHNICIAN', 'ZONAL_COORDINATOR',
  'STATE_SUPERVISOR', 'LGA_SUPERVISOR', 'WARD_SUPERVISOR', 'PU_AGENT',
] as const;
export const USER_STATUSES = ['ACTIVE', 'SUSPENDED', 'REVOKED'] as const;
export const USER_SCOPES = ['NATIONAL', 'ZONE', 'STATE', 'LGA', 'WARD', 'PU'] as const;
export function canManageUsers(user: { backend_role?: string }): boolean {
  return user.backend_role === 'SUPER_ADMIN';
}

export const ROLE_SCOPE: Record<typeof USER_ROLES[number], typeof USER_SCOPES[number]> = {
  SUPER_ADMIN: 'NATIONAL', NATIONAL_ADMINISTRATOR: 'NATIONAL', IT_TECHNICIAN: 'NATIONAL',
  ZONAL_COORDINATOR: 'ZONE', STATE_SUPERVISOR: 'STATE', LGA_SUPERVISOR: 'LGA',
  WARD_SUPERVISOR: 'WARD', PU_AGENT: 'PU',
};

export const USER_PARENT_FIELDS = ['geozone_id', 'state_id', 'lga_id', 'ward_id', 'pu_id'] as const;
export type UserParentField = typeof USER_PARENT_FIELDS[number];
export const ROLE_PARENT_FIELD: Record<typeof USER_ROLES[number], UserParentField | null> = {
  SUPER_ADMIN: null, NATIONAL_ADMINISTRATOR: null, IT_TECHNICIAN: null,
  ZONAL_COORDINATOR: 'geozone_id', STATE_SUPERVISOR: 'state_id', LGA_SUPERVISOR: 'lga_id',
  WARD_SUPERVISOR: 'ward_id', PU_AGENT: 'pu_id',
};

export interface ManagedUser extends Record<UserParentField, string | null> {
  id: string;
  name: string;
  email: string | null;
  phone_number: string;
  role: typeof USER_ROLES[number];
  status: typeof USER_STATUSES[number];
  updated_at: string | null;
}

export interface UserPayload extends Partial<Record<UserParentField, string | null>> {
  name: string;
  email: string | null;
  phone_number: string;
  password?: string;
  role: typeof USER_ROLES[number];
  status?: typeof USER_STATUSES[number];
}

export const GEOGRAPHY_FILTERS = [
  ['geozone_id', 'Geozone UUID'], ['state_id', 'State UUID'], ['lga_id', 'LGA UUID'],
  ['ward_id', 'Ward UUID'], ['pu_id', 'Polling unit UUID'],
] as const;

export type UserFilters = {
  search: string;
  role: string;
  status: string;
  geozone_id: string;
  state_id: string;
  lga_id: string;
  ward_id: string;
  pu_id: string;
  per_page: string;
};

export const EMPTY_USER_FILTERS: UserFilters = {
  search: '', role: '', status: '', geozone_id: '', state_id: '', lga_id: '',
  ward_id: '', pu_id: '', per_page: '50',
};

export type FieldErrors = Record<string, string[]>;

export function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

export function isUserId(value: unknown): value is string {
  return typeof value === 'string' &&
    /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
}

export function userLabel(value: string): string {
  return value.toLowerCase().split('_').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}

function length(value: string) {
  return Array.from(value).length;
}

export function validateUserQuery(query: URLSearchParams): FieldErrors {
  const errors: FieldErrors = {};
  const limits: Record<string, number> = {
    search: 255, geozone_id: 36, state_id: 36, lga_id: 36, ward_id: 36, pu_id: 36,
  };
  const allowed = [...Object.keys(limits), 'role', 'status', 'page', 'per_page'];
  query.forEach((value, key) => {
    if (!allowed.includes(key)) errors[key] = ['Unsupported filter.'];
    else if (query.getAll(key).length !== 1) errors[key] = ['Supply this filter only once.'];
    else if (limits[key] && length(value) > limits[key]) errors[key] = [`Use at most ${limits[key]} characters.`];
    else if (USER_PARENT_FIELDS.some((field) => field === key) && !isUserId(value)) errors[key] = ['Select a valid geographic UUID.'];
    else if (key === 'role' && !USER_ROLES.some((role) => role === value)) errors[key] = ['Choose a valid role.'];
    else if (key === 'status' && !USER_STATUSES.some((status) => status === value)) errors[key] = ['Choose a valid status.'];
    else if ((key === 'page' || key === 'per_page') &&
      (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)) ||
        (key === 'per_page' && Number(value) > 100))) {
      errors[key] = [key === 'page' ? 'Page must be an integer of at least 1.' : 'Page size must be an integer from 1 to 100.'];
    }
  });
  return errors;
}

export function validateUserPayload(value: unknown, mode: 'create' | 'update' = 'create'): FieldErrors {
  if (!isRecord(value)) return { form: ['A JSON object is required.'] };
  const errors: FieldErrors = {};
  for (const [field, max] of [['name', 255], ['phone_number', 32]] as const) {
    if (typeof value[field] !== 'string' || !value[field].trim()) errors[field] = ['This field is required.'];
    else if (length(value[field]) > max) errors[field] = [`Use at most ${max} characters.`];
  }
  if (value.email !== undefined && value.email !== null &&
      (typeof value.email !== 'string' || length(value.email) > 255 ||
        !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.email))) {
    errors.email = ['Enter a valid email address of at most 255 characters, or leave it empty.'];
  }
  const unchangedPassword = mode === 'update' && (value.password === undefined || value.password === '' || value.password === null);
  if (!unchangedPassword && (typeof value.password !== 'string' || length(value.password) < 12 || length(value.password) > 255)) {
    errors.password = ['Password must contain 12 to 255 characters.'];
  }
  if (!USER_ROLES.some((role) => role === value.role)) errors.role = ['Choose a valid role.'];
  const role = USER_ROLES.find((role) => role === value.role);
  for (const field of USER_PARENT_FIELDS) {
    if (value[field] !== undefined && value[field] !== null && !isUserId(value[field])) {
      errors[field] = ['Select a valid geographic UUID.'];
    }
    if (role && ROLE_PARENT_FIELD[role] === null && value[field] != null) {
      errors[field] = ['National accounts cannot have geographic assignments.'];
    }
  }
  const parentField = role ? ROLE_PARENT_FIELD[role] : null;
  if (mode === 'create' && role && parentField && !isUserId(value[parentField])) {
    errors[parentField] = [`Select the ${userLabel(ROLE_SCOPE[role])} assignment required for this role.`];
  }
  if (value.status !== undefined && !USER_STATUSES.some((status) => status === value.status)) {
    errors.status = ['Choose a valid status.'];
  }
  for (const field of Object.keys(value)) {
    if (!['name', 'email', 'phone_number', 'password', 'role', 'status', ...USER_PARENT_FIELDS].includes(field)) {
      errors[field] = ['Unsupported field.'];
    }
  }
  return errors;
}

export function isManagedUser(value: unknown): value is ManagedUser {
  return isRecord(value) &&
    isUserId(value.id) &&
    ['name', 'phone_number'].every((key) => typeof value[key] === 'string') &&
    (value.email === null || typeof value.email === 'string') &&
    USER_ROLES.some((role) => role === value.role) &&
    USER_PARENT_FIELDS.every((field) => value[field] === null || typeof value[field] === 'string') &&
    USER_STATUSES.some((status) => status === value.status) &&
    (value.updated_at === null || (typeof value.updated_at === 'string' && Number.isFinite(Date.parse(value.updated_at))));
}

export interface UserPage {
  data: ManagedUser[];
  links: { first: string | null; last: string | null; prev: string | null; next: string | null };
  meta: {
    current_page: number; last_page: number; per_page: number; total: number;
    from: number | null; to: number | null;
  };
}

export function isUserPage(value: unknown): value is UserPage {
  if (!isRecord(value) || !Array.isArray(value.data) || !value.data.every(isManagedUser) ||
      !isRecord(value.meta) || !isRecord(value.links)) return false;
  const { meta, links } = value;
  return ['current_page', 'last_page', 'per_page'].every((key) =>
    typeof meta[key] === 'number' && Number.isSafeInteger(meta[key]) && meta[key] >= 1) &&
    typeof meta.total === 'number' && Number.isSafeInteger(meta.total) && meta.total >= 0 &&
    typeof meta.per_page === 'number' && meta.per_page <= 100 &&
    ['from', 'to'].every((key) => meta[key] === null ||
      (typeof meta[key] === 'number' && Number.isSafeInteger(meta[key]) && meta[key] >= 1)) &&
    ['first', 'last', 'prev', 'next'].every((key) => links[key] === null || typeof links[key] === 'string');
}
