import db from '@/db/models';
import ForbiddenError from '@/shared/errors/forbidden';
import { ROLE_SCOPES } from '@/shared/constants/roles';
import {
  assertAuthenticatedTenantUser,
  hasGlobalAccess,
  getOrganizationId,
  getRoleScope,
  getSchoolId,
  getCampusId,
} from '@/services/shared/access';
import type { CurrentUser } from '@/db/api/types';

export type TenantChildLevel = 'organization' | 'school' | 'campus' | 'class';

interface TenantChild {
  level: TenantChildLevel;
  id: string;
  name: string | null;
  logo: string | null;
  description?: string | null;
  address?: string | null;
  phone?: string | null;
  email?: string | null;
}

const UUID_RE =
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

function requireUuid(value: unknown): string {
  if (typeof value !== 'string' || !UUID_RE.test(value)) {
    throw new ForbiddenError();
  }
  return value;
}

/** Resolves a campus's owning school + org (for scope validation). */
async function campusTenant(
  campusId: string,
): Promise<{ organizationId: string | null; schoolId: string | null } | null> {
  const campus = await db.campuses.findByPk(campusId, {
    attributes: ['organizationId', 'schoolId'],
  });
  return campus
    ? { organizationId: campus.organizationId, schoolId: campus.schoolId }
    : null;
}

async function assertSchoolInScope(
  schoolId: string,
  currentUser?: CurrentUser,
): Promise<void> {
  if (hasGlobalAccess(currentUser)) return;
  const scope = getRoleScope(currentUser);
  if (scope === ROLE_SCOPES.SCHOOL) {
    if (getSchoolId(currentUser) === schoolId) return;
    throw new ForbiddenError();
  }
  // organization tier: the school must belong to the user's org.
  const school = await db.schools.findByPk(schoolId, {
    attributes: ['organizationId'],
  });
  if (school && school.organizationId === getOrganizationId(currentUser)) return;
  throw new ForbiddenError();
}

async function assertCampusInScope(
  campusId: string,
  currentUser?: CurrentUser,
): Promise<void> {
  if (hasGlobalAccess(currentUser)) return;
  const scope = getRoleScope(currentUser);
  if (scope === ROLE_SCOPES.CAMPUS || scope === ROLE_SCOPES.CLASS) {
    if (getCampusId(currentUser) === campusId) return;
    throw new ForbiddenError();
  }
  const tenant = await campusTenant(campusId);
  if (!tenant) throw new ForbiddenError();
  if (scope === ROLE_SCOPES.SCHOOL) {
    if (tenant.schoolId === getSchoolId(currentUser)) return;
    throw new ForbiddenError();
  }
  if (tenant.organizationId === getOrganizationId(currentUser)) return;
  throw new ForbiddenError();
}

function toChildren(
  level: TenantChildLevel,
  rows: Array<{
    id: string;
    name: string | null;
    logo?: string | null;
    description?: string | null;
    address?: string | null;
    phone?: string | null;
    email?: string | null;
  }>,
): TenantChild[] {
  return rows.map((r) => ({
    level,
    id: r.id,
    name: r.name,
    logo: r.logo ?? null,
    ...(r.description !== undefined ? { description: r.description } : {}),
    ...(r.address !== undefined ? { address: r.address } : {}),
    ...(r.phone !== undefined ? { phone: r.phone } : {}),
    ...(r.email !== undefined ? { email: r.email } : {}),
  }));
}

class ScopeService {
  /**
   * The tenants the user can drill into below `parent` (or below their own
   * tenant when no parent is given): org → schools → campuses → classes.
   */
  static async listChildren(
    parentLevel: unknown,
    parentId: unknown,
    limit: number | undefined,
    currentUser?: CurrentUser,
  ): Promise<{ rows: TenantChild[] }> {
    assertAuthenticatedTenantUser(currentUser);

    const level = parentLevel ? String(parentLevel) : null;
    const id = parentId ? requireUuid(parentId) : null;
    const cleanLimit =
      typeof limit === 'number' && Number.isFinite(limit) && limit > 0
        ? Math.floor(limit)
        : undefined;

    // Default: children of the user's own tenant level.
    if (!level || !id) {
      return { rows: await ScopeService.ownChildren(currentUser, cleanLimit) };
    }

    if (level === 'organization') {
      if (!hasGlobalAccess(currentUser) && id !== getOrganizationId(currentUser)) {
        throw new ForbiddenError();
      }
      const schools = await db.schools.findAll({
        where: { organizationId: id },
        attributes: ['id', 'name', 'logo', 'description', 'address', 'phone', 'email'],
        order: [['name', 'ASC']],
        ...(cleanLimit ? { limit: cleanLimit } : {}),
      });
      return { rows: toChildren('school', schools) };
    }
    if (level === 'school') {
      await assertSchoolInScope(id, currentUser);
      const campuses = await db.campuses.findAll({
        where: { schoolId: id },
        attributes: ['id', 'name', 'logo', 'description', 'address', 'phone', 'email'],
        order: [['name', 'ASC']],
        ...(cleanLimit ? { limit: cleanLimit } : {}),
      });
      return { rows: toChildren('campus', campuses) };
    }
    if (level === 'campus') {
      await assertCampusInScope(id, currentUser);
      const classes = await db.classes.findAll({
        where: { campusId: id },
        attributes: ['id', 'name', 'logo'],
        order: [['name', 'ASC']],
        ...(cleanLimit ? { limit: cleanLimit } : {}),
      });
      return { rows: toChildren('class', classes) };
    }
    return { rows: [] };
  }

  /**
   * Validates a requested drill-down target against the caller's scope and
   * resolves its full tenant chain (org/school/campus/class ids). The result is
   * attached to `currentUser.activeScope` by the active-scope middleware.
   */
  static async resolveActiveScope(
    level: string,
    id: string,
    currentUser?: CurrentUser,
  ): Promise<{
    level: TenantChildLevel;
    organizationId: string | null;
    schoolId: string | null;
    campusId: string | null;
    classId: string | null;
  }> {
    assertAuthenticatedTenantUser(currentUser);
    const cleanId = requireUuid(id);

    if (level === 'organization') {
      if (
        !hasGlobalAccess(currentUser) &&
        cleanId !== getOrganizationId(currentUser)
      ) {
        throw new ForbiddenError();
      }
      return { level: 'organization', organizationId: cleanId, schoolId: null, campusId: null, classId: null };
    }
    if (level === 'school') {
      await assertSchoolInScope(cleanId, currentUser);
      const school = await db.schools.findByPk(cleanId, { attributes: ['organizationId'] });
      if (!school) throw new ForbiddenError();
      return { level: 'school', organizationId: school.organizationId, schoolId: cleanId, campusId: null, classId: null };
    }
    if (level === 'campus') {
      await assertCampusInScope(cleanId, currentUser);
      const tenant = await campusTenant(cleanId);
      if (!tenant) throw new ForbiddenError();
      return { level: 'campus', organizationId: tenant.organizationId, schoolId: tenant.schoolId, campusId: cleanId, classId: null };
    }
    if (level === 'class') {
      const cls = await db.classes.findByPk(cleanId, { attributes: ['campusId', 'organizationId'] });
      if (!cls || !cls.campusId) throw new ForbiddenError();
      await assertCampusInScope(cls.campusId, currentUser);
      const tenant = await campusTenant(cls.campusId);
      return {
        level: 'class',
        organizationId: cls.organizationId ?? tenant?.organizationId ?? null,
        schoolId: tenant?.schoolId ?? null,
        campusId: cls.campusId,
        classId: cleanId,
      };
    }
    throw new ForbiddenError();
  }

  private static async ownChildren(
    currentUser?: CurrentUser,
    limit?: number,
  ): Promise<TenantChild[]> {
    const global = hasGlobalAccess(currentUser);
    const orgId = getOrganizationId(currentUser);
    const scope = getRoleScope(currentUser);

    if (global) {
      const orgs = await db.organizations.findAll({
        attributes: ['id', 'name', 'logo'],
        order: [['name', 'ASC']],
        ...(limit ? { limit } : {}),
      });
      return toChildren('organization', orgs);
    }
    if (scope === ROLE_SCOPES.ORGANIZATION && orgId) {
      const schools = await db.schools.findAll({
        where: { organizationId: orgId },
        attributes: ['id', 'name', 'logo', 'description', 'address', 'phone', 'email'],
        order: [['name', 'ASC']],
        ...(limit ? { limit } : {}),
      });
      return toChildren('school', schools);
    }
    if (scope === ROLE_SCOPES.SCHOOL) {
      const schoolId = getSchoolId(currentUser);
      if (!schoolId) return [];
      const campuses = await db.campuses.findAll({
        where: { schoolId },
        attributes: ['id', 'name', 'logo', 'description', 'address', 'phone', 'email'],
        order: [['name', 'ASC']],
        ...(limit ? { limit } : {}),
      });
      return toChildren('campus', campuses);
    }
    if (scope === ROLE_SCOPES.CAMPUS) {
      const campusId = getCampusId(currentUser);
      if (!campusId) return [];
      const classes = await db.classes.findAll({
        where: { campusId },
        attributes: ['id', 'name', 'logo'],
        order: [['name', 'ASC']],
        ...(limit ? { limit } : {}),
      });
      return toChildren('class', classes);
    }
    return [];
  }
}

export default ScopeService;
