import { Op } from 'sequelize';
import db from '@/db/models';
import ForbiddenError from '@/shared/errors/forbidden';
import ValidationError from '@/shared/errors/validation';
import {
  assertAuthenticatedTenantUser,
  hasFeaturePermission,
  hasGlobalAccess,
  getCampusId,
  getOrganizationId,
  getRoleScope,
  getSchoolId,
  requireUserId,
} from '@/services/shared/access';
import {
  COMMUNICATION_EVENT_TYPE_VALUES,
  type CommunicationEventType,
} from '@/shared/constants/communications';
import { FEATURE_PERMISSIONS } from '@/shared/constants/product-permissions';
import { ROLE_NAMES, ROLE_SCOPES, type RoleName } from '@/shared/constants/roles';
import { resolvePagination } from '@/shared/constants/pagination';
import type { CommunicationEvents } from '@/db/models/communication_events';
import type { CurrentUser } from '@/db/api/types';
import type {
  EventCancelInput,
  EventFilter,
  EventInput,
  EventUpdateInput,
} from '@/services/communications.types';

const SYSTEM_EVENT_ROLES: RoleName[] = [
  ROLE_NAMES.SUPER_ADMIN,
  ROLE_NAMES.SYSTEM_ADMIN,
];
const ORGANIZATION_EVENT_ROLES: RoleName[] = [
  ROLE_NAMES.OWNER,
  ROLE_NAMES.SUPERINTENDENT,
];
const SCHOOL_EVENT_ROLES: RoleName[] = [
  ROLE_NAMES.PRINCIPAL,
  ROLE_NAMES.REGISTRAR,
];
const CAMPUS_EVENT_ROLES: RoleName[] = [
  ROLE_NAMES.DIRECTOR,
  ROLE_NAMES.OFFICE_MANAGER,
  ROLE_NAMES.TEACHER,
  ROLE_NAMES.SUPPORT_STAFF,
];
const ALL_USER_EVENT_ROLES: RoleName[] = [
  ROLE_NAMES.SUPER_ADMIN,
  ROLE_NAMES.SYSTEM_ADMIN,
  ROLE_NAMES.OWNER,
  ROLE_NAMES.SUPERINTENDENT,
  ROLE_NAMES.PRINCIPAL,
  ROLE_NAMES.REGISTRAR,
  ROLE_NAMES.DIRECTOR,
  ROLE_NAMES.OFFICE_MANAGER,
  ROLE_NAMES.TEACHER,
  ROLE_NAMES.SUPPORT_STAFF,
  ROLE_NAMES.STUDENT,
  ROLE_NAMES.GUARDIAN,
];
const ROLE_NAME_LIST: readonly RoleName[] = Object.values(ROLE_NAMES);
const TARGET_LEVELS = ['system', 'all', 'organization', 'school', 'campus'] as const;

type CommunicationEventTargetLevel = (typeof TARGET_LEVELS)[number];

interface CommunicationEventTarget {
  level: CommunicationEventTargetLevel;
  id: string | null;
}

interface CommunicationEventStamp {
  targetLevel: CommunicationEventTargetLevel;
  organizationId: string | null;
  schoolId: string | null;
  campusId: string | null;
  classId: null;
  roles: RoleName[];
}

function requireEventType(value: unknown): CommunicationEventType {
  const type = requiredString(value);
  const match = COMMUNICATION_EVENT_TYPE_VALUES.find((item) => item === type);
  if (!match) {
    throw new ValidationError();
  }
  return match;
}

function assertCanManageCommunications(currentUser?: CurrentUser): void {
  assertAuthenticatedTenantUser(currentUser);

  if (
    currentUser?.app_role?.globalAccess === true
    ||
    hasFeaturePermission(
      currentUser,
      FEATURE_PERMISSIONS.MANAGE_INTERNAL_COMM,
    )
  ) {
    return;
  }

  throw new ForbiddenError();
}

function requiredString(value: unknown): string {
  if (typeof value !== 'string' || value.trim().length === 0) {
    throw new ValidationError();
  }

  return value.trim();
}

function validateRoles(roles: unknown, defaultRoles: readonly RoleName[]): RoleName[] {
  if (!Array.isArray(roles) || roles.length === 0) {
    return [...defaultRoles];
  }

  return roles.map((role: unknown) => {
    const normalized = typeof role === 'string' ? role.trim() : role;
    const match = ROLE_NAME_LIST.find((item) => item === normalized);
    if (!match) {
      throw new ValidationError();
    }
    return match;
  });
}

function targetDefaultRoles(level: CommunicationEventTargetLevel): readonly RoleName[] {
  if (level === 'system') return SYSTEM_EVENT_ROLES;
  if (level === 'all') return ALL_USER_EVENT_ROLES;
  if (level === 'organization') return ORGANIZATION_EVENT_ROLES;
  if (level === 'school') return SCHOOL_EVENT_ROLES;
  return CAMPUS_EVENT_ROLES;
}

function isTargetLevel(value: unknown): value is CommunicationEventTargetLevel {
  return typeof value === 'string' && TARGET_LEVELS.some((level) => level === value);
}

function optionalString(value: unknown): string | null {
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
}

function optionalTrimmedString(value: unknown): string | undefined {
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}

function requireTargetId(target: CommunicationEventTarget): string {
  if (!target.id) {
    throw new ValidationError();
  }
  return target.id;
}

function normalizeTargets(value: unknown, currentUser?: CurrentUser): CommunicationEventTarget[] {
  if (Array.isArray(value) && value.length > 0) {
    return value.map((raw) => {
      if (!raw || typeof raw !== 'object' || !('level' in raw) || !isTargetLevel(raw.level)) {
        throw new ValidationError();
      }
      const id = 'id' in raw ? optionalString(raw.id) : null;
      if ((raw.level === 'system' || raw.level === 'all') && id) {
        throw new ValidationError();
      }
      if ((raw.level === 'organization' || raw.level === 'school' || raw.level === 'campus') && !id) {
        throw new ValidationError();
      }
      return { level: raw.level, id };
    });
  }

  if (hasGlobalAccess(currentUser)) {
    return [{ level: 'system', id: null }];
  }

  const scope = getRoleScope(currentUser);
  if (scope === ROLE_SCOPES.ORGANIZATION) {
    return [{ level: 'organization', id: getOrganizationId(currentUser) }];
  }
  if (scope === ROLE_SCOPES.SCHOOL) {
    return [{ level: 'school', id: getSchoolId(currentUser) }];
  }
  if (scope === ROLE_SCOPES.CAMPUS) {
    return [{ level: 'campus', id: getCampusId(currentUser) }];
  }

  throw new ForbiddenError();
}

async function resolveTarget(
  target: CommunicationEventTarget,
  currentUser?: CurrentUser,
): Promise<CommunicationEventStamp> {
  const scope = getRoleScope(currentUser);

  if (target.level === 'system' || target.level === 'all') {
    if (!hasGlobalAccess(currentUser)) {
      throw new ForbiddenError();
    }
    return {
      targetLevel: target.level,
      organizationId: null,
      schoolId: null,
      campusId: null,
      classId: null,
      roles: [...targetDefaultRoles(target.level)],
    };
  }

  if (target.level === 'organization') {
    if (
      !hasGlobalAccess(currentUser)
      && scope !== ROLE_SCOPES.SYSTEM
      && scope !== ROLE_SCOPES.ORGANIZATION
    ) {
      throw new ForbiddenError();
    }
    const organization = await db.organizations.findByPk(requireTargetId(target), { attributes: ['id'] });
    if (!organization) throw new ForbiddenError();
    if (!hasGlobalAccess(currentUser) && organization.id !== getOrganizationId(currentUser)) {
      throw new ForbiddenError();
    }
    return {
      targetLevel: 'organization',
      organizationId: organization.id,
      schoolId: null,
      campusId: null,
      classId: null,
      roles: [...ORGANIZATION_EVENT_ROLES],
    };
  }

  if (target.level === 'school') {
    if (
      !hasGlobalAccess(currentUser)
      &&
      scope !== ROLE_SCOPES.SYSTEM
      && scope !== ROLE_SCOPES.ORGANIZATION
      && scope !== ROLE_SCOPES.SCHOOL
    ) {
      throw new ForbiddenError();
    }
    const school = await db.schools.findByPk(requireTargetId(target), { attributes: ['id', 'organizationId'] });
    if (!school) throw new ForbiddenError();
    if (!hasGlobalAccess(currentUser)) {
      if (scope === ROLE_SCOPES.ORGANIZATION && school.organizationId !== getOrganizationId(currentUser)) {
        throw new ForbiddenError();
      }
      if (scope === ROLE_SCOPES.SCHOOL && school.id !== getSchoolId(currentUser)) {
        throw new ForbiddenError();
      }
    }
    return {
      targetLevel: 'school',
      organizationId: school.organizationId,
      schoolId: school.id,
      campusId: null,
      classId: null,
      roles: [...SCHOOL_EVENT_ROLES],
    };
  }

  if (scope === ROLE_SCOPES.CLASS) {
    throw new ForbiddenError();
  }
  const campus = await db.campuses.findByPk(requireTargetId(target), {
    attributes: ['id', 'organizationId', 'schoolId'],
  });
  if (!campus) throw new ForbiddenError();
  if (!hasGlobalAccess(currentUser)) {
    if (scope === ROLE_SCOPES.ORGANIZATION && campus.organizationId !== getOrganizationId(currentUser)) {
      throw new ForbiddenError();
    }
    if (scope === ROLE_SCOPES.SCHOOL && campus.schoolId !== getSchoolId(currentUser)) {
      throw new ForbiddenError();
    }
    if (scope === ROLE_SCOPES.CAMPUS && campus.id !== getCampusId(currentUser)) {
      throw new ForbiddenError();
    }
  }
  return {
    targetLevel: 'campus',
    organizationId: campus.organizationId,
    schoolId: campus.schoolId,
    campusId: campus.id,
    classId: null,
    roles: [...CAMPUS_EVENT_ROLES],
  };
}

function communicationEventWhere(currentUser?: CurrentUser) {
  const allUsers = {
    targetLevel: 'all',
    organizationId: null,
    schoolId: null,
    campusId: null,
    classId: null,
  };

  if (hasGlobalAccess(currentUser)) {
    const createdById = requireUserId(currentUser);
    return {
      [Op.or]: [
        allUsers,
        {
          targetLevel: 'system',
          organizationId: null,
          schoolId: null,
          campusId: null,
          classId: null,
        },
        { targetLevel: 'organization', createdById },
        { targetLevel: 'school', createdById },
        { targetLevel: 'campus', createdById },
      ],
    };
  }

  const scope = getRoleScope(currentUser);
  const organizationId = getOrganizationId(currentUser);
  const schoolId = getSchoolId(currentUser);
  const campusId = getCampusId(currentUser);

  if (scope === ROLE_SCOPES.ORGANIZATION) {
    return {
      [Op.or]: [
        allUsers,
        { targetLevel: 'organization', organizationId, schoolId: null, campusId: null, classId: null },
        { targetLevel: 'school', organizationId, campusId: null, classId: null },
        { targetLevel: 'campus', organizationId, classId: null },
      ],
    };
  }
  if (scope === ROLE_SCOPES.SCHOOL) {
    return {
      [Op.or]: [
        allUsers,
        { targetLevel: 'school', organizationId, schoolId, campusId: null, classId: null },
        { targetLevel: 'campus', organizationId, schoolId, classId: null },
      ],
    };
  }
  if (scope === ROLE_SCOPES.CAMPUS || scope === ROLE_SCOPES.CLASS) {
    return {
      [Op.or]: [
        allUsers,
        { targetLevel: 'campus', organizationId, campusId, classId: null },
      ],
    };
  }

  return { [Op.or]: [allUsers] };
}

function eventTenantMatchesManagerScope(
  event: CommunicationEvents,
  currentUser?: CurrentUser,
): boolean {
  if (hasGlobalAccess(currentUser)) {
    return true;
  }
  if (!hasFeaturePermission(currentUser, FEATURE_PERMISSIONS.MANAGE_INTERNAL_COMM)) {
    return false;
  }
  const scope = getRoleScope(currentUser);
  const plain = event.get({ plain: true });

  if (plain.targetLevel === 'system' || plain.targetLevel === 'all') {
    return false;
  }
  if (scope === ROLE_SCOPES.ORGANIZATION) {
    return plain.organizationId === getOrganizationId(currentUser);
  }
  if (scope === ROLE_SCOPES.SCHOOL) {
    return (
      (plain.targetLevel === 'school' || plain.targetLevel === 'campus')
      && plain.organizationId === getOrganizationId(currentUser)
      && plain.schoolId === getSchoolId(currentUser)
    );
  }
  if (scope === ROLE_SCOPES.CAMPUS) {
    return (
      plain.targetLevel === 'campus'
      && plain.organizationId === getOrganizationId(currentUser)
      && plain.campusId === getCampusId(currentUser)
    );
  }

  return false;
}

async function findMutableEvent(id: string, currentUser?: CurrentUser): Promise<CommunicationEvents> {
  assertAuthenticatedTenantUser(currentUser);
  const event = await db.communication_events.findByPk(requiredString(id));
  if (!event) {
    throw new ForbiddenError();
  }
  const plain = event.get({ plain: true });
  if (plain.canceledEventId) {
    throw new ForbiddenError();
  }
  if (plain.createdById === currentUser?.id || eventTenantMatchesManagerScope(event, currentUser)) {
    return event;
  }

  throw new ForbiddenError();
}

function toCommunicationEventDto(event: CommunicationEvents) {
  const plain = event.get({ plain: true });

  return {
    id: plain.id,
    title: plain.title,
    date: plain.event_date,
    type: plain.event_type,
    targetLevel: plain.targetLevel,
    roles: plain.roles,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    schoolId: plain.schoolId,
    classId: plain.classId,
    canceledEventId: plain.canceledEventId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

class CommunicationsService {
  static async listEvents(filter: EventFilter, currentUser?: CurrentUser) {
    assertAuthenticatedTenantUser(currentUser);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);

    const result = await db.communication_events.findAndCountAll({
      where: {
        ...communicationEventWhere(currentUser),
        ...(filter.type ? { event_type: filter.type } : {}),
      },
      order: [
        ['event_date', 'asc'],
        ['createdAt', 'desc'],
      ],
      limit,
      offset,
    });

    return {
      rows: result.rows.map(toCommunicationEventDto),
      count: result.count,
    };
  }

  static async createEvent(data: EventInput, currentUser?: CurrentUser) {
    assertCanManageCommunications(currentUser);

    const title = requiredString(data?.title);
    const date = requiredString(data?.date);
    const type = requireEventType(data?.type);

    const targets = normalizeTargets(data?.targets, currentUser);
    const stamps = await Promise.all(targets.map((target) => resolveTarget(target, currentUser)));
    const createdEvents = await Promise.all(stamps.map((stamp) => db.communication_events.create({
      title,
      event_date: date,
      event_type: type,
      targetLevel: stamp.targetLevel,
      roles: validateRoles(data?.roles, stamp.roles),
      organizationId: stamp.organizationId,
      schoolId: stamp.schoolId,
      campusId: stamp.campusId,
      classId: stamp.classId,
      createdById: requireUserId(currentUser),
      updatedById: currentUser?.id ?? null,
    })));

    return toCommunicationEventDto(createdEvents[0]);
  }

  static async updateEvent(id: string, data: EventUpdateInput, currentUser?: CurrentUser) {
    const event = await findMutableEvent(id, currentUser);
    const update: Partial<{
      title: string;
      event_date: string;
      event_type: CommunicationEventType;
      targetLevel: CommunicationEventTargetLevel;
      roles: RoleName[];
      organizationId: string | null;
      schoolId: string | null;
      campusId: string | null;
      classId: null;
      updatedById: string | null;
    }> = {
      updatedById: currentUser?.id ?? null,
    };

    if (data?.title !== undefined) update.title = requiredString(data.title);
    if (data?.date !== undefined) update.event_date = requiredString(data.date);
    if (data?.type !== undefined) update.event_type = requireEventType(data.type);
    if (data?.roles !== undefined) update.roles = validateRoles(data.roles, event.roles);
    if (data?.targets !== undefined) {
      const targets = normalizeTargets(data.targets, currentUser);
      if (targets.length !== 1) {
        throw new ValidationError();
      }
      const stamp = await resolveTarget(targets[0], currentUser);
      update.targetLevel = stamp.targetLevel;
      update.organizationId = stamp.organizationId;
      update.schoolId = stamp.schoolId;
      update.campusId = stamp.campusId;
      update.classId = stamp.classId;
      if (data?.roles === undefined) update.roles = [...stamp.roles];
    }

    await event.update(update);
    return toCommunicationEventDto(event);
  }

  static async deleteEvent(id: string, currentUser?: CurrentUser) {
    const event = await findMutableEvent(id, currentUser);
    await event.destroy();
  }

  static async cancelEvent(id: string, data: EventCancelInput, currentUser?: CurrentUser) {
    const event = await findMutableEvent(id, currentUser);
    const plain = event.get({ plain: true });
    const reason = optionalTrimmedString(data?.reason);
    const cancellation = await db.communication_events.create({
      title: reason ? `Canceled: ${plain.title} - ${reason}` : `Canceled: ${plain.title}`,
      event_date: plain.event_date,
      event_type: plain.event_type,
      targetLevel: plain.targetLevel,
      roles: plain.roles,
      organizationId: plain.organizationId,
      schoolId: plain.schoolId,
      campusId: plain.campusId,
      classId: plain.classId,
      canceledEventId: plain.id,
      createdById: requireUserId(currentUser),
      updatedById: currentUser?.id ?? null,
    });
    await event.destroy();

    return toCommunicationEventDto(cancellation);
  }
}

export default CommunicationsService;
