import { CaslAbilityFactory } from '../../authorization/casl-ability.factory';
import type { AuthenticatedUser } from '../../authorization';
import { AgentBusinessScopeFactory } from '../agent-business-scope.factory';
import { AgentToolContextFactory } from '../agent-tool.types';
import { SearchClassesTool } from './search-classes.tool';
import { GetAttendanceSummaryTool } from './get-attendance-summary.tool';
import { SearchRoomsTool } from './search-rooms.tool';
import { GetRoomOccupancySummaryTool } from './get-room-occupancy-summary.tool';
import { SearchBillsTool } from './search-bills.tool';
import { GetDashboardStatsTool } from './get-dashboard-stats.tool';
import { SearchExamsTool } from './search-exams.tool';
import { SearchSchedulesTool } from './search-schedules.tool';
import { SearchDepositsTool } from './search-deposits.tool';
import { SearchExpensesTool } from './search-expenses.tool';
import { SearchClassroomsTool } from './search-classrooms.tool';
import { SearchClassroomRentalsTool } from './search-classroom-rentals.tool';
import { GetSyncStatusTool } from './get-sync-status.tool';

function context(permissions: string[] = [], isSuperAdmin = false) {
  const user: AuthenticatedUser = { id: 7, username: 'teacher', permissions, isSuperAdmin, roles: [] };
  return AgentToolContextFactory.fromAuthenticatedUser(user);
}

const scopes = new AgentBusinessScopeFactory(new CaslAbilityFactory());

describe('agent business tools', () => {
  it('search_classes rejects unknown fields and enforces teacher scope', async () => {
    const service = { agentSearchClasses: jest.fn().mockResolvedValue([]) };
    const tool = new SearchClassesTool(service as never, scopes);
    expect(tool.validate({ userId: 1 }).ok).toBe(false);
    expect(tool.validate({ limit: 51 }).ok).toBe(false);
    await tool.execute({ keyword: '一班' }, context(['class:view']));
    expect(service.agentSearchClasses).toHaveBeenCalledWith(7, false, { keyword: '一班' });
  });

  it('class:edit grants full class and attendance scope', async () => {
    const classService = { agentSearchClasses: jest.fn().mockResolvedValue([]) };
    const attendanceService = { agentGetAttendanceSummary: jest.fn().mockResolvedValue([]) };
    const ctx = context(['class:view', 'class:edit', 'attendance:view']);
    await new SearchClassesTool(classService as never, scopes).execute({}, ctx);
    await new GetAttendanceSummaryTool(attendanceService as never, scopes).execute({}, ctx);
    expect(classService.agentSearchClasses).toHaveBeenCalledWith(7, true, {});
    expect(attendanceService.agentGetAttendanceSummary).toHaveBeenCalledWith(7, true, {});
  });

  it('attendance validates date range and limit', () => {
    const tool = new GetAttendanceSummaryTool({} as never, scopes);
    expect(tool.validate({ dateFrom: '2026-07-23', dateTo: '2026-07-22' }).ok).toBe(false);
    expect(tool.validate({ dateFrom: '2026-02-30' }).ok).toBe(false);
    expect(tool.validate({ limit: 50 }).ok).toBe(true);
  });

  it('room tools reject sensitive/unknown fields and forward safe input', async () => {
    const service = {
      agentSearchRooms: jest.fn().mockResolvedValue([]),
      agentGetRoomOccupancySummary: jest.fn().mockResolvedValue([]),
    };
    const search = new SearchRoomsTool(service as never);
    const summary = new GetRoomOccupancySummaryTool(service as never);
    expect(search.validate({ studentName: '张三' }).ok).toBe(false);
    expect(summary.validate({ permissions: ['room:view'] }).ok).toBe(false);
    await search.execute({ building: '1号楼', limit: 10 }, context(['room:view']));
    await summary.execute({ date: '2026-07-23' }, context(['room:view']));
    expect(service.agentSearchRooms).toHaveBeenCalledWith({ building: '1号楼', limit: 10 });
    expect(service.agentGetRoomOccupancySummary).toHaveBeenCalledWith({ date: '2026-07-23' });
  });

  it('bill tool exposes read permission and validates ranges', async () => {
    const service = { agentSearchBills: jest.fn().mockResolvedValue([]) };
    const tool = new SearchBillsTool(service as never);
    expect(tool.requiredPermission).toBe('bill:view');
    expect(tool.validate({ periodStart: '2026-07-31', periodEnd: '2026-07-01' }).ok).toBe(false);
    await tool.execute({ status: 'unpaid', limit: 20 }, context(['bill:view']));
    expect(service.agentSearchBills).toHaveBeenCalledWith({ status: 'unpaid', limit: 20 });
  });

  it('dashboard uses teacher scope unless super admin', async () => {
    const service = { agentGetDashboardStats: jest.fn().mockResolvedValue({}) };
    const tool = new GetDashboardStatsTool(service as never, scopes);
    expect(tool.validate({ debug: true }).ok).toBe(false);
    await tool.execute({}, context(['dashboard:view']));
    await tool.execute({}, context([], true));
    expect(service.agentGetDashboardStats).toHaveBeenNthCalledWith(1, 7, false);
    expect(service.agentGetDashboardStats).toHaveBeenNthCalledWith(2, 7, true);
  });

  it('exam tool enforces scope and rejects unknown fields', async () => {
    const service = { agentSearchExams: jest.fn().mockResolvedValue([]) };
    const tool = new SearchExamsTool(service as never, scopes);
    expect(tool.requiredPermission).toBe('exam:view');
    expect(tool.validate({ userId: 1 }).ok).toBe(false);
    expect(tool.validate({ limit: 51 }).ok).toBe(false);
    await tool.execute({ classId: 3, limit: 10 }, context(['exam:view']));
    expect(service.agentSearchExams).toHaveBeenCalledWith(7, false, { classId: 3, limit: 10 });
    await tool.execute({}, context([], true));
    expect(service.agentSearchExams).toHaveBeenLastCalledWith(7, true, {});
  });

  it('schedule tool validates weekDay range and forwards scope', async () => {
    const service = { agentSearchSchedules: jest.fn().mockResolvedValue([]) };
    const tool = new SearchSchedulesTool(service as never, scopes);
    expect(tool.requiredPermission).toBe('schedule:view');
    expect(tool.validate({ weekDay: 8 }).ok).toBe(false);
    expect(tool.validate({ weekDay: 0 }).ok).toBe(false);
    await tool.execute({ classroomId: 2, weekDay: 3 }, context(['schedule:view']));
    expect(service.agentSearchSchedules).toHaveBeenCalledWith(7, false, {
      classroomId: 2,
      weekDay: 3,
    });
  });

  it('deposit tool validates and forwards safe input', async () => {
    const service = { agentSearchDeposits: jest.fn().mockResolvedValue([]) };
    const tool = new SearchDepositsTool(service as never);
    expect(tool.requiredPermission).toBe('deposit:view');
    expect(tool.validate({ permissions: ['deposit:view'] }).ok).toBe(false);
    await tool.execute({ keyword: '张三', status: 'paid', limit: 10 }, context(['deposit:view']));
    expect(service.agentSearchDeposits).toHaveBeenCalledWith({
      keyword: '张三',
      status: 'paid',
      limit: 10,
    });
  });

  it('expense tool validates period range', async () => {
    const tool = new SearchExpensesTool({} as never);
    expect(tool.validate({ periodStart: '2026-08-01', periodEnd: '2026-07-01' }).ok).toBe(false);
    expect(tool.validate({ periodStart: '2026-02-30' }).ok).toBe(false);
    expect(tool.validate({ limit: 31 }).ok).toBe(false);
    expect(tool.validate({ keyword: '3-301' }).ok).toBe(true);
  });

  it('classroom and rental tools validate inputs', async () => {
    const classroomTool = new SearchClassroomsTool({} as never);
    const rentalTool = new SearchClassroomRentalsTool({} as never);
    expect(classroomTool.requiredPermission).toBe('classroom:view');
    expect(classroomTool.validate({ building: '1号楼' }).ok).toBe(true);
    expect(rentalTool.requiredPermission).toBe('rental:view');
    expect(rentalTool.validate({ month: '2026-13' }).ok).toBe(false);
    expect(rentalTool.validate({ month: '2026-08', includeEnded: 'yes' }).ok).toBe(false);
    expect(rentalTool.validate({ month: '2026-08', includeEnded: true }).ok).toBe(true);
  });

  it('sync status tool rejects any input and forwards nothing', async () => {
    const service = { agentGetSyncStatus: jest.fn().mockResolvedValue({}) };
    const tool = new GetSyncStatusTool(service as never);
    expect(tool.requiredPermission).toBe('sync:read');
    expect(tool.validate({ debug: true }).ok).toBe(false);
    await tool.execute({}, context(['sync:read']));
    expect(service.agentGetSyncStatus).toHaveBeenCalledTimes(1);
  });
});
