import { Injectable, inject } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { map, Observable } from 'rxjs';
import {
  Student, StudentPayload, DeletePayload,
  CreateStudentInput, UpdateStudentInput,
  CreateStudentWithActivationPayload
} from '@core/models';
import { GET_STUDENTS, GET_STUDENT } from '@core/graphql/queries/students.queries';
import { CREATE_STUDENT, UPDATE_STUDENT, DELETE_STUDENT } from '@core/graphql/mutations/students.mutations';

interface StudentsQueryResult {
  students: Student[];
}

interface StudentQueryResult {
  student: Student | null;
}

interface CreateStudentResult {
  createStudent: CreateStudentWithActivationPayload;
}

interface UpdateStudentResult {
  updateStudent: StudentPayload;
}

interface DeleteStudentResult {
  deleteStudent: DeletePayload;
}

/**
 * Service for managing student CRUD operations via GraphQL.
 * Provides reactive data access with Apollo Client caching.
 *
 * @example
 * ```typescript
 * this.studentService.getStudents().subscribe(({ data, loading }) => {
 *   this.students = data;
 * });
 * ```
 */
@Injectable({ providedIn: 'root' })
export class StudentService {
  private apollo = inject(Apollo);

  /**
   * Retrieves all students from the server.
   * Uses cache-and-network policy for optimal UX.
   *
   * @returns Observable emitting students array and loading state
   */
  getStudents(): Observable<{ data: Student[]; loading: boolean }> {
    return this.apollo
      .watchQuery<StudentsQueryResult>({
        query: GET_STUDENTS,
        fetchPolicy: 'cache-and-network',
      })
      .valueChanges.pipe(
        map(result => ({
          data: (result.data?.students ?? []) as Student[],
          loading: result.loading,
        }))
      );
  }

  /**
   * Retrieves a single student by ID.
   *
   * @param id - The student's unique identifier
   * @returns Observable emitting the student (or null if not found) and loading state
   */
  getStudent(id: number): Observable<{ data: Student | null; loading: boolean }> {
    return this.apollo
      .watchQuery<StudentQueryResult>({
        query: GET_STUDENT,
        variables: { id },
        fetchPolicy: 'cache-and-network',
      })
      .valueChanges.pipe(
        map(result => ({
          data: (result.data?.student ?? null) as Student | null,
          loading: result.loading,
        }))
      );
  }

  /**
   * Creates a new student in the system.
   * Returns activation code that must be shared with the student.
   * Automatically refreshes the students list cache on success.
   *
   * @param input - Student data (name, email)
   * @returns Observable with created student and activation info, or validation errors
   * @throws GraphQL errors for validation failures (VALIDATION_ERROR)
   */
  createStudent(input: CreateStudentInput): Observable<CreateStudentWithActivationPayload> {
    return this.apollo
      .mutate<CreateStudentResult>({
        mutation: CREATE_STUDENT,
        variables: { input },
        refetchQueries: [{ query: GET_STUDENTS }],
      })
      .pipe(map(result => result.data?.createStudent ?? {}));
  }

  /**
   * Updates an existing student's information.
   * Automatically refreshes the students list cache on success.
   *
   * @param id - The student's unique identifier
   * @param input - Updated student data (name, email)
   * @returns Observable with updated student or validation errors
   * @throws GraphQL errors: STUDENT_NOT_FOUND, VALIDATION_ERROR
   */
  updateStudent(id: number, input: UpdateStudentInput): Observable<StudentPayload> {
    return this.apollo
      .mutate<UpdateStudentResult>({
        mutation: UPDATE_STUDENT,
        variables: { id, input },
        refetchQueries: [{ query: GET_STUDENTS }],
      })
      .pipe(map(result => result.data?.updateStudent ?? {}));
  }

  /**
   * Deletes a student from the system.
   * Also removes all associated enrollments (cascade delete).
   * Automatically refreshes the students list cache on success.
   *
   * @param id - The student's unique identifier
   * @returns Observable with success status or errors
   * @throws GraphQL errors: STUDENT_NOT_FOUND
   */
  deleteStudent(id: number): Observable<DeletePayload> {
    return this.apollo
      .mutate<DeleteStudentResult>({
        mutation: DELETE_STUDENT,
        variables: { id },
        refetchQueries: [{ query: GET_STUDENTS }],
      })
      .pipe(map(result => result.data?.deleteStudent ?? { success: false }));
  }
}
