package org.fuseri.modulelanguageschool.course;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import org.fuseri.model.dto.course.CourseCreateDto;
import org.fuseri.model.dto.course.CourseDto;
import org.fuseri.model.dto.course.LanguageTypeDto;
import org.fuseri.model.dto.course.ProficiencyLevelDto;
import org.fuseri.modulelanguageschool.ModuleLanguageSchoolApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * This class represents a RESTful controller for Courses resources.
 * It handles incoming HTTP requests related to courses, and delegates them to the appropriate service method.
 */
@Api(tags = "Course Controller")
@RestController
@RequestMapping("/courses")
public class CourseController {

    private final CourseFacade courseFacade;

    @Autowired
    public CourseController(CourseFacade courseFacade) {
        this.courseFacade = courseFacade;
    }

    /**
     * Creates a new course.
     *
     * @param dto the CourseCreateDto containing the course data
     * @return the newly created CourseDto
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Create a new course")
    @PostMapping
    @ApiResponses({
            @ApiResponse(code = 201, message = "Course created successfully"),
            @ApiResponse(code = 400, message = "Invalid request body")
    })
    public ResponseEntity<CourseDto> create(@Valid @RequestBody CourseCreateDto dto) {
        CourseDto courseDto = courseFacade.create(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(courseDto);
    }

    /**
     * Retrieves a course by ID.
     *
     * @param id the ID of the course to retrieve
     * @return the CourseDto for the specified ID
     */
    @ApiOperation(value = "Retrieve a course by ID")
    @GetMapping("/find/{id}")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Course found"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    public ResponseEntity<CourseDto> find(@PathVariable Long id) {
        CourseDto courseDto = courseFacade.findById(id);
        return ResponseEntity.ok(courseDto);
    }

    /**
     * Retrieves a paginated list of courses
     *
     * @param page the page number to retrieve
     * @return the Result containing the requested page of CourseDtos
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Retrieve a paginated list of courses")
    @GetMapping("/findAll")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved courses"),
            @ApiResponse(code = 404, message = "No courses found")
    })
    public ResponseEntity<Page<CourseDto>> findAll(@RequestParam int page) {
        Page<CourseDto> courseDtoPage = courseFacade.findAll(PageRequest.of(page, 10, Sort.by(Sort.Direction.ASC, "name")));
        return ResponseEntity.ok(courseDtoPage);
    }

    /**
     * Retrieves a paginated list of courses of a given language
     *
     * @param lang the language to find courses of
     * @return the Result containing the requested page of CourseDtos
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Retrieve a paginated list of courses of a given language")
    @GetMapping("/findAllByLang")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Courses found"),
            @ApiResponse(code = 400, message = "Invalid request body")
    })
    public ResponseEntity<List<CourseDto>> findAll(@RequestParam LanguageTypeDto lang) {
        List<CourseDto> courseDtos = courseFacade.findAll(lang);
        return ResponseEntity.ok(courseDtos);
    }

    /**
     * Retrieves a paginated list of courses of a given language and proficiency
     *
     * @param lang the language to find courses of
     * @param prof the proficiency of the language
     * @return the Result containing the requested page of CourseDtos
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Retrieve a paginated list of courses of a given language and proficiency")
    @GetMapping("/findAllByLangProf")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Courses found"),
            @ApiResponse(code = 400, message = "Invalid request body")
    })
    public ResponseEntity<List<CourseDto>> findAll(@RequestParam LanguageTypeDto lang,
                                                   @RequestParam ProficiencyLevelDto prof) {
        List<CourseDto> courses = courseFacade.findAll(lang, prof);
        return ResponseEntity.ok(courses);
    }

    /**
     * Updates an existing course.
     *
     * @param id  the ID of the course to update
     * @param dto the CourseCreateDto containing the updated course data
     * @return the updated CourseDto
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Update an existing course")
    @PutMapping("/update/{id}")
    @ApiResponses({
            @ApiResponse(code = 200, message = "Course updated successfully"),
            @ApiResponse(code = 400, message = "Invalid request body"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    public ResponseEntity<CourseDto> update(@PathVariable Long id, @Valid @RequestBody CourseCreateDto dto) {
        CourseDto updatedCourse = courseFacade.update(id, dto);
        return ResponseEntity.ok(updatedCourse);
    }

    /**
     * Deletes a course by ID.
     *
     * @param id the ID of the course to delete
     */
    @ApiOperation(value = "Delete a course by ID")
    @DeleteMapping("/delete/{id}")
    @ApiResponses({
            @ApiResponse(code = 204, message = "Course deleted successfully"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        courseFacade.delete(id);
        return ResponseEntity.noContent().build();
    }

    /**
     * Adds student to the existing course
     *
     * @param id        id of course to update
     * @param studentId id of the student
     * @return the CourseDto representing the updated course
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Add student to the existing course")
    @PatchMapping("/enrolStudent/{id}")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully enrolled student in course"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    public ResponseEntity<CourseDto> enrol(@PathVariable Long id, @RequestParam Long studentId) {
        CourseDto updatedCourse = courseFacade.enrol(id, studentId);
        return ResponseEntity.ok(updatedCourse);
    }

    /**
     * Adds currently signed-in student to the existing course
     *
     * @param id        id of lecture to update
     * @param principal http request received with user email
     * @return the CourseDto representing the updated course
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Add me to the existing course")
    @PatchMapping("/enrol/{id}")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully enrolled student in course"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    public ResponseEntity<CourseDto> enrol(@PathVariable Long id, @AuthenticationPrincipal OAuth2IntrospectionAuthenticatedPrincipal principal) throws JsonProcessingException {
        String email = principal.getSubject();
        return ResponseEntity.ok(courseFacade.enrol(id, email));
    }

    /**
     * Removes student from the existing course
     *
     * @param id        id of lecture to update
     * @param studentId ID of the student
     * @return the CourseDto representing the updated course
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Remove student from the existing course")
    @PatchMapping("/expelStudent/{id}")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully expelled student from course"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    public ResponseEntity<CourseDto> expel(@PathVariable Long id, @RequestParam Long studentId) {
        return ResponseEntity.ok(courseFacade.expel(id, studentId));
    }

    /**
     * Removes currently signed-in student from the existing course
     *
     * @param id        id of lecture to update
     * @param principal http request received with user email
     * @return the CourseDto representing the updated course
     */
    @Operation(security = @SecurityRequirement(name = ModuleLanguageSchoolApplication.SECURITY_SCHEME_NAME))
    @ApiOperation(value = "Remove me from the existing course")
    @PatchMapping("/expel/{id}")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully expelled student in course"),
            @ApiResponse(code = 404, message = "Course not found")
    })
    public ResponseEntity<CourseDto> expel(@PathVariable Long id, @AuthenticationPrincipal OAuth2IntrospectionAuthenticatedPrincipal principal) throws JsonProcessingException {
        String email = principal.getSubject();
        return ResponseEntity.ok(courseFacade.expel(id, email));
    }

}
