package fr.titionfire.ffsaf.rest;

import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.domain.service.ClubService;
import fr.titionfire.ffsaf.domain.service.PDFService;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.domain.service.VirusScannerService;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.FullClubForm;
import fr.titionfire.ffsaf.rest.from.PartClubForm;
import fr.titionfire.ffsaf.utils.Contact;
import fr.titionfire.ffsaf.utils.PageResult;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;

@Tag(name = "Club", description = "Gestion des clubs")
@Path("api/club")
public class ClubEndpoints {

    @Inject
    ClubService clubService;

    @Inject
    PDFService pdfService;

    @Inject
    SecurityCtx securityCtx;

    @Inject
    VirusScannerService scannerService;

    @ConfigProperty(name = "upload_dir")
    String media;

    @Inject
    TradService trad;

    Consumer<ClubModel> checkPerm = Unchecked.consumer(clubModel -> {
        if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(clubModel.getId()))
            throw new DForbiddenException();
    });
    Consumer<Long> checkPerm2 = Unchecked.consumer(id -> {
        if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(id))
            throw new DForbiddenException();
    });

    @GET
    @Path("/no_detail")
    @Authenticated
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Renvoie la liste de tous les clubs sans détails", description = "Renvoie la liste de tous les " +
            "clubs sans les détails des membres et des affiliations")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "La liste de tous les clubs sans détails"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<List<SimpleClubModel>> getAll() {
        return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).sorted(
                Comparator.comparing(SimpleClubModel::getName)).toList());
    }

    @GET
    @Path("/contact_type")
    @Authenticated
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Renvoie les types de contacts pour les clubs", description = "Renvoie la liste des types de " +
            "contacts possibles pour les clubs")
    public Uni<HashMap<String, String>> getConcatType() {
        return Uni.createFrom().item(Contact.toSite(trad));
    }

    @GET
    @Path("/find")
    @RolesAllowed({"federation_admin"})
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Recherche des clubs en fonction de critères de recherche", description = "Recherche des clubs " +
            "en fonction de critères de recherche tels que le nom, le pays, etc.")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "La liste des clubs correspondant aux critères de recherche"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<PageResult<SimpleClubList>> getFindAdmin(
            @Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit,
            @Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
            @Parameter(description = "Text à rechercher") @QueryParam("search") String search,
            @Parameter(description = "Pays à filter") @QueryParam("country") String country) {
        if (limit == null)
            limit = 50;
        if (page == null || page < 1)
            page = 1;
        return clubService.search(limit, page - 1, search, country);
    }


    @GET
    @Path("{id}")
    @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Renvoie les détails d'un club en fonction de son identifiant", description = "Renvoie les " +
            "détails d'un club en fonction de son identifiant, y compris les informations sur les membres et les affiliations")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Les détails du club"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<SimpleClub> getById(
            @Parameter(description = "Identifiant de club") @PathParam("id") long id) {
        return clubService.getById(id).onItem().invoke(checkPerm).map(SimpleClub::fromModel)
                .invoke(m -> m.setContactMap(Contact.toSite(trad)));
    }

    @PUT
    @Path("{id}")
    @RolesAllowed({"federation_admin"})
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(summary = "Met à jour les informations d'un club en fonction de son identifiant", description = "Met à " +
            "jour les informations d'un club en fonction de son identifiant, y compris les informations sur les membres" +
            " et les affiliations")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Le club a été mis à jour avec succès"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<String> setAdminClub(
            @Parameter(description = "Identifiant de club") @PathParam("id") long id, FullClubForm input) {
        return clubService.update(id, input)
                .invoke(Unchecked.consumer(out -> {
                    if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
                }))
                .chain(() -> Utils.uploadFile(scannerService, input.getLogo(), id, media, "ppClub"))
                .chain(() -> Utils.uploadFile(scannerService, input.getStatus(), id, media, "clubStatus"));
    }

    @PUT
    @RolesAllowed({"federation_admin"})
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(summary = "Ajoute un nouveau club", description = "Ajoute un nouveau club avec les informations fournies" +
            " dans le formulaire")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Le club a été ajouté avec succès"),
            @APIResponse(responseCode = "400", description = "Les données envoyées sont invalides"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<Long> addAdminClub(FullClubForm input) {
        return clubService.add(input)
                .invoke(Unchecked.consumer(id -> {
                    if (id == null) throw new InternalError("Fail to create club data");
                }))
                .call(id -> Utils.uploadFile(scannerService, input.getLogo(), id, media, "ppClub"))
                .call(id -> Utils.uploadFile(scannerService, input.getStatus(), id, media, "clubStatus"));
    }

    @DELETE
    @Path("{id}")
    @RolesAllowed({"federation_admin"})
    @Produces(MediaType.TEXT_PLAIN)
    @Operation(summary = "Supprime un club en fonction de son identifiant", description = "Supprime un club en fonction" +
            " de son identifiant, ainsi que toutes les informations associées")
    @APIResponses(value = {
            @APIResponse(responseCode = "204", description = "Le club a été supprimé avec succès"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<?> deleteAdminClub(
            @Parameter(description = "Identifiant de club") @PathParam("id") long id) {
        return clubService.delete(id);
    }

    @GET
    @Path("/{id}/affiliation")
    @RolesAllowed({"federation_admin"})
    @Operation(summary = "Renvoie l'attestation d'affiliation du club en fonction de son identifiant", description =
            "Renvoie l'attestation d'affiliation du club en fonction de son identifiant")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "L'attestation d'affiliation"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas ou n'a pas d'affiliation active"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<Response> getAffiliation(@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
        return pdfService.getAffiliationPdf(id);
    }

    @GET
    @Path("/me")
    @RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Renvoie les informations du club de l'utilisateur connecté", description = "Renvoie les " +
            "informations du club de l'utilisateur connecté")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Les informations du club de l'utilisateur connecté"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<SimpleClub> getOfUser() {
        return clubService.getOfUser(securityCtx).map(SimpleClub::fromModel)
                .invoke(m -> m.setContactMap(Contact.toSite(trad)));
    }

    @PUT
    @Path("/me")
    @RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(summary = "Met à jour les informations du club de l'utilisateur connecté", description = "Met à jour les" +
            " informations du club de l'utilisateur connecté")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Les informations du club de l'utilisateur connecté ont été mises à jour avec succès"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<String> setClubOfUser(PartClubForm form) {
        return clubService.updateOfUser(securityCtx, form);
    }

    @GET
    @Path("/me/affiliation")
    @RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Operation(summary = "Renvoie l'attestation d'affiliation du club de l'utilisateur connecté", description =
            "Renvoie l'attestation d'affiliation du club de l'utilisateur connecté")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "L'attestation d'affiliation"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'a pas d'affiliation active"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<Response> getMeAffiliation() {
        return pdfService.getAffiliationPdf(securityCtx.getSubject());
    }


    @GET
    @Path("/members")
    @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra", "club_tresorier"})
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Revoie tout les membres de votre club")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "List des membres"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<List<VerySimpleMembre>> getMembers() {
        return clubService.getMembers(securityCtx);
    }


    @GET
    @Path("/renew/{id}")
    @RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(hidden = true)
    public Uni<RenewAffData> getRenew(@PathParam("id") long id, @QueryParam("m1") long m1_id,
                                      @QueryParam("m2") long m2_id, @QueryParam("m3") long m3_id) {
        return Uni.createFrom().item(id).invoke(checkPerm2)
                .chain(__ -> clubService.getRenewData(id, List.of(m1_id, m2_id, m3_id)));
    }

    @GET
    @Path("/desk/{id}")
    @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(summary = "Renvoie la liste des membres du bureau du club", description = "Renvoie la liste des membres " +
            "du bureau du club spécifié")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "La liste des membres du bureau du club"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<List<DeskMember>> getClubDesk(
            @Parameter(description = "Identifiant de club") @PathParam("id") long id) {
        return clubService.getClubDesk(checkPerm, id);
    }

    @GET
    @Path("{clubId}/logo")
    @Operation(summary = "Renvoie le logo du club", description = "Renvoie le logo du club spécifié")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Le logo du club"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<Response> getLogo(
            @Parameter(description = "Identifiant long (clubId) de club") @PathParam("clubId") String clubId) {
        return clubService.getByClubId(clubId).chain(Unchecked.function(clubModel -> {
            try {
                return Utils.getMediaFileNoDefault((clubModel != null) ? clubModel.getId() : -1, media, "ppClub",
                        Uni.createFrom().nullItem());
            } catch (URISyntaxException e) {
                throw new InternalError();
            }
        }));
    }

    @GET
    @Path("{id}/status")
    @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
    @Operation(summary = "Renvoie le statut du club", description = "Renvoie le statut du club spécifié")
    @APIResponses(value = {
            @APIResponse(responseCode = "200", description = "Le statut du club"),
            @APIResponse(responseCode = "403", description = "Accès refusé"),
            @APIResponse(responseCode = "404", description = "Le club n'existe pas"),
            @APIResponse(responseCode = "500", description = "Erreur interne du serveur")
    })
    public Uni<Response> getStatus(
            @Parameter(description = "Identifiant de club") @PathParam("id") long id) {
        return clubService.getById(id).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> {
            try {
                return Utils.getMediaFile(clubModel.getId(), media, "clubStatus",
                        "statue-" + clubModel.getName(), Uni.createFrom().nullItem(), false);
            } catch (URISyntaxException e) {
                throw new InternalError();
            }
        }));
    }

    @GET
    @Path("get_map_data")
    public Uni<List<ClubMapData>> getMapData() {
        return clubService.getMapData();
    }

}
