import { afterEach, describe, mock, test } from 'node:test';
import assert from 'node:assert/strict';
import { Op } from 'sequelize';

import db from '@/db/models';
import CommunicationsService from '@/services/communications';
import { FEATURE_PERMISSIONS } from '@/shared/constants/product-permissions';
import { ROLE_SCOPES } from '@/shared/constants/roles';
import ForbiddenError from '@/shared/errors/forbidden';
import { createGlobalAccessUser, createTestUser } from '@/test-utils';

function permission(name: string) {
  return { name };
}

function assertScopeArray(value: unknown): asserts value is Array<Record<string, unknown>> {
  assert.ok(Array.isArray(value));
}

function eventRecord(overrides: Record<string, unknown> = {}) {
  const data = {
    id: 'event-1',
    title: 'Alert',
    event_date: '2026-06-15',
    event_type: 'event',
    targetLevel: 'campus',
    roles: ['director'],
    organizationId: 'org-1',
    campusId: 'campus-1',
    schoolId: null,
    classId: null,
    canceledEventId: null,
    createdById: 'user-1',
    updatedById: 'user-1',
    createdAt: new Date('2026-06-15T00:00:00Z'),
    updatedAt: new Date('2026-06-15T00:00:00Z'),
    ...overrides,
  };
  return {
    update: async (payload: Record<string, unknown>) => {
      Object.assign(data, payload);
      return eventRecord(data);
    },
    destroy: async () => undefined,
    get roles() {
      return data.roles;
    },
    get: () => ({
      ...data,
    }),
  };
}

function managerUser(overrides: Parameters<typeof createTestUser>[0] = {}) {
  return createTestUser({
    id: 'manager-1',
    organizationId: 'org-1',
    organizations: { id: 'org-1' },
    app_role: {
      name: 'director',
      scope: ROLE_SCOPES.CAMPUS,
      globalAccess: false,
      permissions: [permission(FEATURE_PERMISSIONS.MANAGE_INTERNAL_COMM)],
    },
    campusId: 'campus-1',
    ...overrides,
  });
}

afterEach(() => {
  mock.restoreAll();
});

describe('CommunicationsService event targets', () => {
  test('creates system-only platform alerts for global managers by default', async () => {
    const createPayloads: Record<string, unknown>[] = [];
    mock.method(db.communication_events, 'create', (async (payload: Record<string, unknown>) => {
      createPayloads.push(payload);
      return eventRecord(payload);
    }) as typeof db.communication_events.create);

    await CommunicationsService.createEvent(
      { title: 'Global alert', date: '2026-06-15', type: 'event' },
      createGlobalAccessUser({ id: 'admin-1' }),
    );

    assert.equal(createPayloads[0].targetLevel, 'system');
    assert.equal(createPayloads[0].organizationId, null);
    assert.equal(createPayloads[0].schoolId, null);
    assert.equal(createPayloads[0].campusId, null);
    assert.deepEqual(createPayloads[0].roles, ['super_admin', 'system_admin']);
  });

  test('creates one exact target row per selected organization', async () => {
    const createPayloads: Record<string, unknown>[] = [];
    mock.method(db.organizations, 'findByPk', (async () => ({ id: 'org-1' })) as typeof db.organizations.findByPk);
    mock.method(db.communication_events, 'create', (async (payload: Record<string, unknown>) => {
      createPayloads.push(payload);
      return eventRecord(payload);
    }) as typeof db.communication_events.create);

    await CommunicationsService.createEvent(
      {
        title: 'Org alert',
        date: '2026-06-15',
        type: 'event',
        targets: [{ level: 'organization', id: 'org-1' }],
      },
      createGlobalAccessUser({ id: 'admin-1' }),
    );

    assert.equal(createPayloads[0].targetLevel, 'organization');
    assert.equal(createPayloads[0].organizationId, 'org-1');
    assert.equal(createPayloads[0].schoolId, null);
    assert.equal(createPayloads[0].campusId, null);
    assert.deepEqual(createPayloads[0].roles, ['owner', 'superintendent']);
  });

  test('creates school and campus rows when an organization manager selects both levels', async () => {
    const createPayloads: Record<string, unknown>[] = [];
    mock.method(db.schools, 'findByPk', (async () => ({ id: 'school-1', organizationId: 'org-1' })) as typeof db.schools.findByPk);
    mock.method(db.campuses, 'findByPk', (async () => ({
      id: 'campus-1',
      organizationId: 'org-1',
      schoolId: 'school-1',
    })) as typeof db.campuses.findByPk);
    mock.method(db.communication_events, 'create', (async (payload: Record<string, unknown>) => {
      createPayloads.push(payload);
      return eventRecord(payload);
    }) as typeof db.communication_events.create);

    await CommunicationsService.createEvent(
      {
        title: 'School and campus alert',
        date: '2026-06-15',
        type: 'event',
        targets: [
          { level: 'school', id: 'school-1' },
          { level: 'campus', id: 'campus-1' },
        ],
      },
      managerUser({
        app_role: {
          name: 'owner',
          scope: ROLE_SCOPES.ORGANIZATION,
          globalAccess: false,
          permissions: [permission(FEATURE_PERMISSIONS.MANAGE_INTERNAL_COMM)],
        },
      }),
    );

    assert.equal(createPayloads.length, 2);
    assert.equal(createPayloads[0].targetLevel, 'school');
    assert.deepEqual(createPayloads[0].roles, ['principal', 'registrar']);
    assert.equal(createPayloads[1].targetLevel, 'campus');
    assert.deepEqual(createPayloads[1].roles, ['director', 'office_manager', 'teacher', 'support_staff']);
  });

  test('rejects alert creation without MANAGE_INTERNAL_COMM for tenant users', async () => {
    await assert.rejects(
      () => CommunicationsService.createEvent(
        { title: 'Read-only alert', date: '2026-06-15', type: 'event' },
        managerUser({
          app_role: {
            name: 'office_manager',
            scope: ROLE_SCOPES.CAMPUS,
            globalAccess: false,
            permissions: [],
          },
        }),
      ),
      ForbiddenError,
    );
  });

  test('creates campus alerts for campus managers only inside their own campus', async () => {
    const createPayloads: Record<string, unknown>[] = [];
    mock.method(db.campuses, 'findByPk', (async () => ({
      id: 'campus-1',
      organizationId: 'org-1',
      schoolId: 'school-1',
    })) as typeof db.campuses.findByPk);
    mock.method(db.communication_events, 'create', (async (payload: Record<string, unknown>) => {
      createPayloads.push(payload);
      return eventRecord(payload);
    }) as typeof db.communication_events.create);

    await CommunicationsService.createEvent(
      {
        title: 'Campus alert',
        date: '2026-06-15',
        type: 'event',
        targets: [{ level: 'campus', id: 'campus-1' }],
      },
      managerUser(),
    );

    assert.equal(createPayloads[0].targetLevel, 'campus');
    assert.equal(createPayloads[0].organizationId, 'org-1');
    assert.equal(createPayloads[0].schoolId, 'school-1');
    assert.equal(createPayloads[0].campusId, 'campus-1');
  });

  test('rejects campus manager attempts to create school-level alerts', async () => {
    await assert.rejects(
      () => CommunicationsService.createEvent(
        {
          title: 'School alert',
          date: '2026-06-15',
          type: 'event',
          targets: [{ level: 'school', id: 'school-1' }],
        },
        managerUser(),
      ),
      ForbiddenError,
    );
  });

  test('rejects school manager attempts to create campus alerts outside their school', async () => {
    mock.method(db.campuses, 'findByPk', (async () => ({
      id: 'campus-2',
      organizationId: 'org-1',
      schoolId: 'school-2',
    })) as typeof db.campuses.findByPk);

    await assert.rejects(
      () => CommunicationsService.createEvent(
        {
          title: 'Wrong campus alert',
          date: '2026-06-15',
          type: 'event',
          targets: [{ level: 'campus', id: 'campus-2' }],
        },
        managerUser({
          schoolId: 'school-1',
          app_role: {
            name: 'principal',
            scope: ROLE_SCOPES.SCHOOL,
            globalAccess: false,
            permissions: [permission(FEATURE_PERMISSIONS.MANAGE_INTERNAL_COMM)],
          },
        }),
      ),
      ForbiddenError,
    );
  });

  test('updates alerts for the creator', async () => {
    const event = eventRecord({ createdById: 'manager-1' });
    mock.method(db.communication_events, 'findByPk', (async () => event) as typeof db.communication_events.findByPk);

    const updated = await CommunicationsService.updateEvent(
      'event-1',
      { title: 'Updated alert', date: '2026-06-20', type: 'deadline' },
      managerUser(),
    );

    assert.equal(updated.title, 'Updated alert');
    assert.equal(updated.date, '2026-06-20');
    assert.equal(updated.type, 'deadline');
    assert.equal(updated.updatedById, 'manager-1');
  });

  test('rejects alert updates by non-creator users without manager scope', async () => {
    mock.method(
      db.communication_events,
      'findByPk',
      (async () => eventRecord({ createdById: 'other-user', campusId: 'other-campus' })) as typeof db.communication_events.findByPk,
    );

    await assert.rejects(
      () => CommunicationsService.updateEvent(
        'event-1',
        { title: 'Rejected update' },
        managerUser(),
      ),
      ForbiddenError,
    );
  });

  test('deletes alerts for scoped managers', async () => {
    let deleted = false;
    const event = {
      ...eventRecord({ createdById: 'other-user', campusId: 'campus-1' }),
      destroy: async () => {
        deleted = true;
      },
    };
    mock.method(db.communication_events, 'findByPk', (async () => event) as typeof db.communication_events.findByPk);

    await CommunicationsService.deleteEvent('event-1', managerUser());

    assert.equal(deleted, true);
  });

  test('canceling an alert creates a cancellation notification and deletes the original', async () => {
    let deleted = false;
    const createPayloads: Record<string, unknown>[] = [];
    const event = {
      ...eventRecord({ createdById: 'manager-1', title: 'Team meeting' }),
      destroy: async () => {
        deleted = true;
      },
    };
    mock.method(db.communication_events, 'findByPk', (async () => event) as typeof db.communication_events.findByPk);
    mock.method(db.communication_events, 'create', (async (payload: Record<string, unknown>) => {
      createPayloads.push(payload);
      return eventRecord(payload);
    }) as typeof db.communication_events.create);

    const cancellation = await CommunicationsService.cancelEvent(
      'event-1',
      { reason: 'weather' },
      managerUser(),
    );

    assert.equal(deleted, true);
    assert.equal(createPayloads[0].title, 'Canceled: Team meeting - weather');
    assert.equal(createPayloads[0].canceledEventId, 'event-1');
    assert.equal(createPayloads[0].targetLevel, 'campus');
    assert.equal(cancellation.canceledEventId, 'event-1');
  });

  test('rejects mutating cancellation notifications', async () => {
    mock.method(
      db.communication_events,
      'findByPk',
      (async () => eventRecord({ canceledEventId: 'original-event-1', createdById: 'manager-1' })) as typeof db.communication_events.findByPk,
    );

    await assert.rejects(
      () => CommunicationsService.updateEvent('event-1', { title: 'Nope' }, managerUser()),
      ForbiddenError,
    );
    await assert.rejects(
      () => CommunicationsService.deleteEvent('event-1', managerUser()),
      ForbiddenError,
    );
    await assert.rejects(
      () => CommunicationsService.cancelEvent('event-1', {}, managerUser()),
      ForbiddenError,
    );
  });

  test('lists platform alerts and own tenant-target alerts for global root users', async () => {
    let listScopes: Array<Record<string, unknown>> = [];
    mock.method(
      db.communication_events,
      'findAndCountAll',
      (async (query: { where: Record<string | symbol, unknown> }) => {
        const scopes = query.where[Op.or];
        assertScopeArray(scopes);
        listScopes = scopes;
        return { rows: [eventRecord()], count: 1 };
      }) as typeof db.communication_events.findAndCountAll,
    );

    await CommunicationsService.listEvents({}, createGlobalAccessUser({ id: 'admin-1' }));

    assert.equal(listScopes.length, 5);
    assert.deepEqual(
      listScopes.map((scope) => scope.targetLevel),
      ['all', 'system', 'organization', 'school', 'campus'],
    );
    assert.equal(listScopes[2].createdById, 'admin-1');
    assert.equal(listScopes[3].createdById, 'admin-1');
    assert.equal(listScopes[4].createdById, 'admin-1');
  });

  test('lists tenant alerts for global users only when drilled into that tenant scope', async () => {
    let listScopes: Array<Record<string, unknown>> = [];
    mock.method(
      db.communication_events,
      'findAndCountAll',
      (async (query: { where: Record<string | symbol, unknown> }) => {
        const scopes = query.where[Op.or];
        assertScopeArray(scopes);
        listScopes = scopes;
        return { rows: [eventRecord()], count: 1 };
      }) as typeof db.communication_events.findAndCountAll,
    );

    await CommunicationsService.listEvents({}, createGlobalAccessUser({
      id: 'admin-1',
      activeScope: {
        level: 'organization',
        organizationId: 'org-1',
        schoolId: null,
        campusId: null,
        classId: null,
      },
    }));

    assert.equal(listScopes.length, 4);
    assert.deepEqual(
      listScopes.map((scope) => scope.targetLevel),
      ['all', 'organization', 'school', 'campus'],
    );
    assert.equal(listScopes[1].organizationId, 'org-1');
    assert.equal(listScopes[2].organizationId, 'org-1');
    assert.equal(listScopes[3].organizationId, 'org-1');
  });

  test('lists descendant school and campus target alerts for organization-scope users', async () => {
    let listScopes: Array<Record<string, unknown>> = [];
    mock.method(
      db.communication_events,
      'findAndCountAll',
      (async (query: { where: Record<string | symbol, unknown> }) => {
        const scopes = query.where[Op.or];
        assertScopeArray(scopes);
        listScopes = scopes;
        return { rows: [eventRecord()], count: 1 };
      }) as typeof db.communication_events.findAndCountAll,
    );

    await CommunicationsService.listEvents({}, managerUser({
      app_role: {
        name: 'owner',
        scope: ROLE_SCOPES.ORGANIZATION,
        globalAccess: false,
        permissions: [],
      },
    }));

    assert.equal(listScopes.length, 4);
    assert.deepEqual(
      listScopes.map((scope) => scope.targetLevel),
      ['all', 'organization', 'school', 'campus'],
    );
    assert.equal(listScopes[2].organizationId, 'org-1');
    assert.equal(listScopes[3].organizationId, 'org-1');
  });

  test('lists descendant campus target alerts for school-scope users', async () => {
    let listScopes: Array<Record<string, unknown>> = [];
    mock.method(
      db.communication_events,
      'findAndCountAll',
      (async (query: { where: Record<string | symbol, unknown> }) => {
        const scopes = query.where[Op.or];
        assertScopeArray(scopes);
        listScopes = scopes;
        return { rows: [eventRecord()], count: 1 };
      }) as typeof db.communication_events.findAndCountAll,
    );

    await CommunicationsService.listEvents({}, managerUser({
      schoolId: 'school-1',
      app_role: {
        name: 'principal',
        scope: ROLE_SCOPES.SCHOOL,
        globalAccess: false,
        permissions: [],
      },
    }));

    assert.equal(listScopes.length, 3);
    assert.deepEqual(
      listScopes.map((scope) => scope.targetLevel),
      ['all', 'school', 'campus'],
    );
    assert.equal(listScopes[2].schoolId, 'school-1');
  });

  test('lists campus target alerts for class-scope users', async () => {
    let listScopes: Array<Record<string, unknown>> = [];
    mock.method(
      db.communication_events,
      'findAndCountAll',
      (async (query: { where: Record<string | symbol, unknown> }) => {
        const scopes = query.where[Op.or];
        assertScopeArray(scopes);
        listScopes = scopes;
        return { rows: [eventRecord()], count: 1 };
      }) as typeof db.communication_events.findAndCountAll,
    );

    await CommunicationsService.listEvents({}, managerUser({
      campusId: 'campus-1',
      app_role: {
        name: 'teacher',
        scope: ROLE_SCOPES.CLASS,
        globalAccess: false,
        permissions: [],
      },
    }));

    assert.equal(listScopes.length, 2);
    assert.equal(listScopes[1].targetLevel, 'campus');
    assert.equal(listScopes[1].campusId, 'campus-1');
  });

  test('rejects alert creation from class scope', async () => {
    await assert.rejects(
      () => CommunicationsService.createEvent(
        { title: 'Class alert', date: '2026-06-15', type: 'event' },
        managerUser({
          app_role: {
            name: 'teacher',
            scope: ROLE_SCOPES.CLASS,
            globalAccess: false,
            permissions: [permission(FEATURE_PERMISSIONS.MANAGE_INTERNAL_COMM)],
          },
        }),
      ),
      ForbiddenError,
    );
  });
});
