import { Injectable, inject } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { map, Observable } from 'rxjs';
import {
  AvailableSubject, Classmate, EnrollmentPayload, DeletePayload, EnrollInput, Student
} from '@core/models';
import { GET_AVAILABLE_SUBJECTS, GET_CLASSMATES, GET_STUDENT } from '@core/graphql/queries/students.queries';
import { ENROLL_STUDENT, UNENROLL_STUDENT } from '@core/graphql/mutations/students.mutations';

interface AvailableSubjectsResult {
  availableSubjects: AvailableSubject[];
}

interface ClassmatesResult {
  classmates: Classmate[];
}

interface StudentResult {
  student: Student | null;
}

interface EnrollResult {
  enrollStudent: EnrollmentPayload;
}

interface UnenrollResult {
  unenrollStudent: DeletePayload;
}

/**
 * Service for managing student enrollments in subjects.
 * Handles business rules: max 3 subjects, no duplicate professors.
 *
 * @example
 * ```typescript
 * // Enroll student in a subject
 * this.enrollmentService.enrollStudent({ studentId: 1, subjectId: 2 }).subscribe(result => {
 *   if (result.errors?.length) {
 *     // Handle validation errors
 *   }
 * });
 * ```
 */
@Injectable({ providedIn: 'root' })
export class EnrollmentService {
  private apollo = inject(Apollo);

  /**
   * Retrieves a student with their current enrollments.
   *
   * @param studentId - The student's unique identifier
   * @returns Observable with student data including enrollments array
   */
  getStudentWithEnrollments(studentId: number): Observable<{ data: Student | null; loading: boolean }> {
    return this.apollo
      .watchQuery<StudentResult>({
        query: GET_STUDENT,
        variables: { id: studentId },
        fetchPolicy: 'cache-and-network',
      })
      .valueChanges.pipe(
        map(result => ({
          data: (result.data?.student ?? null) as Student | null,
          loading: result.loading,
        }))
      );
  }

  /**
   * Retrieves subjects available for enrollment.
   * Each subject includes availability status based on business rules:
   * - Already enrolled: not available
   * - Same professor: not available
   * - Max subjects reached: not available
   *
   * Uses network-only to ensure fresh validation from server.
   *
   * @param studentId - The student's unique identifier
   * @returns Observable with available subjects and their restrictions
   */
  getAvailableSubjects(studentId: number): Observable<{ data: AvailableSubject[]; loading: boolean }> {
    return this.apollo
      .watchQuery<AvailableSubjectsResult>({
        query: GET_AVAILABLE_SUBJECTS,
        variables: { studentId },
        fetchPolicy: 'network-only',
      })
      .valueChanges.pipe(
        map(result => ({
          data: (result.data?.availableSubjects ?? []) as AvailableSubject[],
          loading: result.loading,
        }))
      );
  }

  /**
   * Retrieves classmates for each subject the student is enrolled in.
   * Returns only names of other students (privacy-aware).
   *
   * @param studentId - The student's unique identifier
   * @returns Observable with classmates grouped by subject
   */
  getClassmates(studentId: number): Observable<{ data: Classmate[]; loading: boolean }> {
    return this.apollo
      .watchQuery<ClassmatesResult>({
        query: GET_CLASSMATES,
        variables: { studentId },
        fetchPolicy: 'cache-and-network',
      })
      .valueChanges.pipe(
        map(result => ({
          data: (result.data?.classmates ?? []) as Classmate[],
          loading: result.loading,
        }))
      );
  }

  /**
   * Enrolls a student in a subject.
   * Validates business rules on server:
   * - Maximum 3 subjects per student (9 credits)
   * - No two subjects with the same professor
   *
   * Automatically refreshes student data and available subjects on success.
   *
   * @param input - Enrollment request with studentId and subjectId
   * @returns Observable with enrollment result or validation errors
   * @throws GraphQL errors: MAX_ENROLLMENTS, SAME_PROFESSOR, DUPLICATE_ENROLLMENT
   */
  enrollStudent(input: EnrollInput): Observable<EnrollmentPayload> {
    return this.apollo
      .mutate<EnrollResult>({
        mutation: ENROLL_STUDENT,
        variables: { input },
        refetchQueries: [
          { query: GET_STUDENT, variables: { id: input.studentId } },
          { query: GET_AVAILABLE_SUBJECTS, variables: { studentId: input.studentId } },
        ],
      })
      .pipe(map(result => result.data?.enrollStudent ?? {}));
  }

  /**
   * Cancels a student's enrollment in a subject.
   * Frees up a slot for another subject enrollment.
   * Automatically refreshes student data and available subjects on success.
   *
   * @param enrollmentId - The enrollment's unique identifier
   * @param studentId - The student's ID (needed for cache refresh)
   * @returns Observable with success status or errors
   * @throws GraphQL errors: ENROLLMENT_NOT_FOUND
   */
  unenrollStudent(enrollmentId: number, studentId: number): Observable<DeletePayload> {
    return this.apollo
      .mutate<UnenrollResult>({
        mutation: UNENROLL_STUDENT,
        variables: { enrollmentId, studentId },
        refetchQueries: [
          { query: GET_STUDENT, variables: { id: studentId } },
          { query: GET_AVAILABLE_SUBJECTS, variables: { studentId } },
        ],
      })
      .pipe(map(result => result.data?.unenrollStudent ?? { success: false }));
  }
}
