import { Injectable, NotFoundException } from '@nestjs/common';
import { StudentsService } from '../../students/students.service';
import type { UpdateStudentDto } from '../../students/dto/student.dto';
import type { AgentToolContext, ToolDef, ToolInputResult } from '../agent-tool.types';

/** Whitelisted editable fields on a single student update. */
interface UpdateStudentInput {
  id: number;
  name?: string;
  studentNo?: string;
  phone?: string;
  idNumber?: string;
  gender?: string;
  ethnicity?: string;
  emergencyContact?: string;
  emergencyPhone?: string;
  organizationId?: number;
  supervisor?: string;
  status?: 'active' | 'graduated' | 'withdrawn';
}

interface UpdateStudentsInput {
  updates: UpdateStudentInput[];
}

type StringField =
  | 'name'
  | 'studentNo'
  | 'phone'
  | 'idNumber'
  | 'gender'
  | 'ethnicity'
  | 'emergencyContact'
  | 'emergencyPhone'
  | 'supervisor';

/** Forbidden input keys — if the model sends these, validation fails. */
const FORBIDDEN_INPUT_KEYS = new Set([
  'userId',
  'isSuperAdmin',
  'permissions',
  'roles',
  'ability',
  'user',
  'password',
  'token',
]);

const TOP_LEVEL_KEYS = new Set(['updates']);
const ITEM_KEYS = new Set([
  'id',
  'name',
  'studentNo',
  'phone',
  'idNumber',
  'gender',
  'ethnicity',
  'emergencyContact',
  'emergencyPhone',
  'organizationId',
  'supervisor',
  'status',
]);
const STATUS_VALUES = new Set(['active', 'graduated', 'withdrawn']);
const MAX_BATCH_UPDATES = 12;

function isPlainRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}

function optionalString(
  value: unknown,
  max: number,
): { ok: true; value?: string } | { ok: false; error: string } {
  if (value === undefined) return { ok: true };
  if (typeof value !== 'string') return { ok: false, error: '字段必须是字符串' };
  const trimmed = value.trim();
  if (trimmed.length > max) return { ok: false, error: `字段长度不能超过 ${max}` };
  return { ok: true, value: trimmed };
}

/**
 * Batch-updates student profiles from form-confirmed data.
 *
 * This is a write tool. It is only exposed to the model after the user
 * submits a rendered form (see AiChatService), and the executor still
 * enforces `student:edit` at execution time.
 */
@Injectable()
export class UpdateStudentsTool implements ToolDef<UpdateStudentsInput> {
  readonly name = 'update_students';
  readonly skillKey = 'student';
  readonly requiredPermission = 'student:edit';
  readonly inputSchema = {
    type: 'object',
    properties: {
      updates: {
        type: 'array',
        description: '待更新的学生列表（1-12 条，每条必须包含学生 id 和至少一个可编辑字段）',
        minItems: 1,
        maxItems: MAX_BATCH_UPDATES,
        items: {
          type: 'object',
          properties: {
            id: { type: 'integer', description: '学生 ID', minimum: 1 },
            name: { type: 'string', description: '学生姓名', maxLength: 50 },
            studentNo: { type: 'string', description: '学号', maxLength: 30 },
            phone: { type: 'string', description: '手机号', maxLength: 30 },
            idNumber: { type: 'string', description: '身份证号', maxLength: 30 },
            gender: { type: 'string', description: '性别', maxLength: 20 },
            ethnicity: { type: 'string', description: '民族', maxLength: 50 },
            emergencyContact: { type: 'string', description: '紧急联系人', maxLength: 50 },
            emergencyPhone: { type: 'string', description: '紧急联系电话', maxLength: 30 },
            organizationId: { type: 'integer', description: '校区 ID', minimum: 1 },
            supervisor: { type: 'string', description: '负责人', maxLength: 50 },
            status: {
              type: 'string',
              description: '学生状态',
              enum: ['active', 'graduated', 'withdrawn'],
            },
          },
          required: ['id'],
          additionalProperties: false,
        },
      },
    },
    required: ['updates'],
    additionalProperties: false,
  };
  readonly description =
    '根据用户通过表单确认的信息批量修改学生档案（姓名、学号、手机号、身份证、性别、民族、紧急联系人、校区、负责人、状态等）。仅可在表单提交后的轮次使用，不得自行编造或修改字段。';

  constructor(private readonly studentsService: StudentsService) {}

  validate(input: Record<string, unknown>): ToolInputResult<UpdateStudentsInput> {
    for (const key of Object.keys(input)) {
      if (FORBIDDEN_INPUT_KEYS.has(key)) {
        return { ok: false, error: `不允许的输入字段: ${key}` };
      }
      if (!TOP_LEVEL_KEYS.has(key)) {
        return { ok: false, error: `不允许的输入字段: ${key}` };
      }
    }

    if (!Array.isArray(input.updates) || input.updates.length === 0) {
      return { ok: false, error: 'updates 至少需要一条记录' };
    }
    if (input.updates.length > MAX_BATCH_UPDATES) {
      return { ok: false, error: `updates 不能超过 ${MAX_BATCH_UPDATES} 条` };
    }

    const seenIds = new Set<number>();
    const updates: UpdateStudentInput[] = [];
    for (let index = 0; index < input.updates.length; index += 1) {
      const raw = input.updates[index];
      if (!isPlainRecord(raw)) {
        return { ok: false, error: `第 ${index + 1} 条更新格式无效` };
      }
      for (const key of Object.keys(raw)) {
        if (FORBIDDEN_INPUT_KEYS.has(key)) {
          return { ok: false, error: `不允许的输入字段: ${key}` };
        }
        if (!ITEM_KEYS.has(key)) {
          return { ok: false, error: `第 ${index + 1} 条包含未知字段: ${key}` };
        }
      }

      const id = Number(raw.id);
      if (!Number.isInteger(id) || id <= 0) {
        return { ok: false, error: `第 ${index + 1} 条的学生 id 必须是正整数` };
      }
      if (seenIds.has(id)) {
        return { ok: false, error: `学生 id 重复: ${id}` };
      }
      seenIds.add(id);

      const item: UpdateStudentInput = { id };
      const stringFields: Array<[StringField, unknown, number]> = [
        ['name', raw.name, 50],
        ['studentNo', raw.studentNo, 30],
        ['phone', raw.phone, 30],
        ['idNumber', raw.idNumber, 30],
        ['gender', raw.gender, 20],
        ['ethnicity', raw.ethnicity, 50],
        ['emergencyContact', raw.emergencyContact, 50],
        ['emergencyPhone', raw.emergencyPhone, 30],
        ['supervisor', raw.supervisor, 50],
      ];
      for (const [field, value, max] of stringFields) {
        const parsed = optionalString(value, max);
        if (!parsed.ok) {
          return { ok: false, error: `第 ${index + 1} 条 ${String(field)}: ${parsed.error}` };
        }
        if (parsed.value !== undefined) item[field] = parsed.value;
      }

      if (raw.organizationId !== undefined) {
        const organizationId = Number(raw.organizationId);
        if (!Number.isInteger(organizationId) || organizationId <= 0) {
          return { ok: false, error: `第 ${index + 1} 条的 organizationId 必须是正整数` };
        }
        item.organizationId = organizationId;
      }

      if (raw.status !== undefined) {
        if (typeof raw.status !== 'string' || !STATUS_VALUES.has(raw.status)) {
          return { ok: false, error: `第 ${index + 1} 条的 status 无效` };
        }
        item.status = raw.status as UpdateStudentInput['status'];
      }

      if (Object.keys(item).length === 1) {
        return { ok: false, error: `第 ${index + 1} 条至少需要一个可编辑字段` };
      }
      updates.push(item);
    }

    return { ok: true, value: { updates } };
  }

  async execute(input: UpdateStudentsInput, _context: AgentToolContext): Promise<unknown> {
    const updated: Array<{ id: number; name: string | null }> = [];
    const failed: Array<{ id: number; error: string }> = [];

    for (const item of input.updates) {
      const { id: _id, ...rest } = item;
      const dto = rest as UpdateStudentDto;
      try {
        const student = await this.studentsService.update(item.id, dto);
        updated.push({ id: item.id, name: student?.name ?? null });
      } catch (error) {
        failed.push({
          id: item.id,
          error: error instanceof NotFoundException ? '学生不存在' : '更新失败',
        });
      }
    }

    return {
      message: `成功更新 ${updated.length} 名学生，失败 ${failed.length} 条`,
      updated,
      failed,
    };
  }
}
