import { clampLimit, requiredIsoDate } from '@/services/shared/validate';
import { Op } from 'sequelize';
import db from '@/db/models';
import { withTransaction } from '@/db/with-transaction';
import ForbiddenError from '@/shared/errors/forbidden';
import ValidationError from '@/shared/errors/validation';
import {
  CAMPUS_ATTENDANCE_DEFAULT_LIMIT,
  CAMPUS_ATTENDANCE_MANAGER_ROLE_NAMES,
  CAMPUS_ATTENDANCE_MAX_LIMIT,
  CAMPUS_ATTENDANCE_TENANT_WIDE_ROLE_NAMES,
  normalizeCampusKey,
} from '@/shared/constants/campus-attendance';
import { resolvePagination } from '@/shared/constants/pagination';
import type { CampusAttendanceConfig } from '@/db/models/campus_attendance_config';
import type { CampusAttendanceSummaries } from '@/db/models/campus_attendance_summaries';
import type { CurrentUser } from '@/db/api/types';
import type {
  CampusAttendanceFilter,
  ConfigInput,
  SummaryInput,
} from '@/services/campus_attendance.types';
import {
  assertAuthenticatedTenantUser,
  getCampusId,
  hasRoleAccess,
  requireOrganizationId,
  requireUserId,
  getDisplayName,
} from '@/services/shared/access';

function getCurrentUserCampusKey(currentUser?: CurrentUser): string | null {
  const staff = currentUser?.staff_user;
  const staffProfile = Array.isArray(staff) ? staff[0] : null;

  return (
    normalizeCampusKey(currentUser?.campus?.code) ||
    normalizeCampusKey(currentUser?.campus?.name) ||
    normalizeCampusKey(staffProfile?.campus?.code) ||
    normalizeCampusKey(staffProfile?.campus?.name) ||
    null
  );
}

function canManageCampusAttendance(currentUser?: CurrentUser): boolean {
  return hasRoleAccess(currentUser, CAMPUS_ATTENDANCE_MANAGER_ROLE_NAMES);
}

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

  if (canManageCampusAttendance(currentUser)) {
    return;
  }

  throw new ForbiddenError();
}

function campusKeyFromRoute(value: unknown): string {
  const campusKey = normalizeCampusKey(value);

  if (!campusKey) {
    throw new ValidationError();
  }

  return campusKey;
}

function assertCanAccessCampusKey(
  campusKey: string,
  currentUser?: CurrentUser,
): void {
  if (hasRoleAccess(currentUser, CAMPUS_ATTENDANCE_TENANT_WIDE_ROLE_NAMES)) {
    return;
  }

  const currentCampusKey = getCurrentUserCampusKey(currentUser);

  if (currentCampusKey && currentCampusKey === campusKey) {
    return;
  }

  throw new ForbiddenError();
}

/** Resolves the campus_key scope, asserting access along the way. */
function campusScope(
  filter: CampusAttendanceFilter,
  currentUser?: CurrentUser,
): { campus_key?: string } {
  const requestedCampusKey = normalizeCampusKey(filter?.campusKey);

  if (requestedCampusKey) {
    assertCanAccessCampusKey(requestedCampusKey, currentUser);
    return { campus_key: requestedCampusKey };
  }

  if (hasRoleAccess(currentUser, CAMPUS_ATTENDANCE_TENANT_WIDE_ROLE_NAMES)) {
    return {};
  }

  const currentCampusKey = getCurrentUserCampusKey(currentUser);

  if (!currentCampusKey) {
    throw new ForbiddenError();
  }

  return { campus_key: currentCampusKey };
}

function dateRange(filter: CampusAttendanceFilter): {
  attendance_date?: { [Op.gte]?: string; [Op.lte]?: string };
} {
  const start = filter.startDate ? requiredIsoDate(filter.startDate) : null;
  const end = filter.endDate ? requiredIsoDate(filter.endDate) : null;

  if (!start && !end) {
    return {};
  }

  return {
    attendance_date: {
      ...(start ? { [Op.gte]: start } : {}),
      ...(end ? { [Op.lte]: end } : {}),
    },
  };
}

function requiredNonNegativeInteger(value: unknown): number {
  if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
    throw new ValidationError();
  }

  return value;
}

function optionalText(value: unknown): string | null {
  if (typeof value !== 'string' || value.trim().length === 0) {
    return null;
  }

  return value.trim();
}

function validateSummary(data: SummaryInput) {
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
    throw new ValidationError();
  }

  const totalEnrolled = requiredNonNegativeInteger(data.total_enrolled);
  const totalPresent = requiredNonNegativeInteger(data.total_present);
  const totalAbsent = requiredNonNegativeInteger(data.total_absent);
  const totalTardy = requiredNonNegativeInteger(data.total_tardy ?? 0);

  if (
    totalEnrolled <= 0 ||
    totalPresent > totalEnrolled ||
    totalAbsent > totalEnrolled ||
    totalTardy > totalEnrolled
  ) {
    throw new ValidationError();
  }

  return {
    total_enrolled: totalEnrolled,
    total_present: totalPresent,
    total_absent: totalAbsent,
    total_tardy: totalTardy,
    attendance_percentage: ((totalPresent / totalEnrolled) * 100).toFixed(2),
    notes: optionalText(data.notes),
  };
}

function toConfigDto(record: CampusAttendanceConfig) {
  const plain = record.get({ plain: true });

  return {
    id: plain.id,
    campus_key: plain.campus_key,
    attendance_link: plain.attendance_link,
    updated_by_label: plain.updated_by_label,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

function toSummaryDto(record: CampusAttendanceSummaries) {
  const plain = record.get({ plain: true });

  return {
    id: plain.id,
    campus_key: plain.campus_key,
    date: plain.attendance_date,
    total_enrolled: plain.total_enrolled,
    total_present: plain.total_present,
    total_absent: plain.total_absent,
    total_tardy: plain.total_tardy,
    attendance_percentage: Number(plain.attendance_percentage),
    recorded_by_label: plain.recorded_by_label,
    notes: plain.notes,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

class CampusAttendanceService {
  static async listConfigs(
    filter: CampusAttendanceFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);
    const { limit, offset } = resolvePagination(filter.limit, filter.page);

    const result = await db.campus_attendance_config.findAndCountAll({
      where: {
        organizationId: requireOrganizationId(currentUser),
        ...campusScope(filter, currentUser),
      },
      order: [['campus_key', 'asc']],
      limit,
      offset,
    });

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

  static async upsertConfig(
    campusKeyParam: unknown,
    data: ConfigInput,
    currentUser?: CurrentUser,
  ) {
    assertCanManageCampusAttendance(currentUser);

    const campusKey = campusKeyFromRoute(campusKeyParam);
    assertCanAccessCampusKey(campusKey, currentUser);

    if (!data || typeof data !== 'object' || Array.isArray(data)) {
      throw new ValidationError();
    }

    const payload = {
      campus_key: campusKey,
      attendance_link: optionalText(data.attendance_link),
      updated_by_label: getDisplayName(currentUser),
      organizationId: requireOrganizationId(currentUser),
      campusId: getCampusId(currentUser),
      updatedById: currentUser?.id ?? null,
    };
    return withTransaction(async (transaction) => {
      const existing = await db.campus_attendance_config.findOne({
        where: {
          organizationId: requireOrganizationId(currentUser),
          campus_key: campusKey,
        },
        transaction,
      });
      const saved = existing
        ? await existing.update(payload, { transaction })
        : await db.campus_attendance_config.create(
            { ...payload, createdById: requireUserId(currentUser) },
            { transaction },
          );

      return toConfigDto(saved);
    });
  }

  static async listSummaries(
    filter: CampusAttendanceFilter,
    currentUser?: CurrentUser,
  ) {
    assertAuthenticatedTenantUser(currentUser);

    const result = await db.campus_attendance_summaries.findAndCountAll({
      where: {
        organizationId: requireOrganizationId(currentUser),
        ...campusScope(filter, currentUser),
        ...dateRange(filter),
      },
      limit: clampLimit(filter.limit, CAMPUS_ATTENDANCE_DEFAULT_LIMIT, CAMPUS_ATTENDANCE_MAX_LIMIT),
      order: [
        ['attendance_date', 'desc'],
        ['campus_key', 'asc'],
      ],
    });

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

  static async upsertSummary(
    campusKeyParam: unknown,
    dateParam: unknown,
    data: SummaryInput,
    currentUser?: CurrentUser,
  ) {
    assertCanManageCampusAttendance(currentUser);

    const campusKey = campusKeyFromRoute(campusKeyParam);
    const attendanceDate = requiredIsoDate(dateParam);
    assertCanAccessCampusKey(campusKey, currentUser);

    const validatedSummary = validateSummary(data);
    const payload = {
      ...validatedSummary,
      campus_key: campusKey,
      attendance_date: attendanceDate,
      recorded_by_label: getDisplayName(currentUser),
      organizationId: requireOrganizationId(currentUser),
      campusId: getCampusId(currentUser),
      updatedById: currentUser?.id ?? null,
    };
    return withTransaction(async (transaction) => {
      const existing = await db.campus_attendance_summaries.findOne({
        where: {
          organizationId: requireOrganizationId(currentUser),
          campus_key: campusKey,
          attendance_date: attendanceDate,
        },
        transaction,
      });
      const saved = existing
        ? await existing.update(payload, { transaction })
        : await db.campus_attendance_summaries.create(
            { ...payload, createdById: requireUserId(currentUser) },
            { transaction },
          );

      return toSummaryDto(saved);
    });
  }
}

export default CampusAttendanceService;
