import geojson
import io
from flask import send_file
from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError
from distutils.util import strtobool
from backend.models.dtos.project_dto import (
    DraftProjectDTO,
    ProjectDTO,
    ProjectSearchDTO,
    ProjectSearchBBoxDTO,
)
from backend.services.project_search_service import (
    ProjectSearchService,
    ProjectSearchServiceError,
    BBoxTooBigError,
)
from backend.services.project_service import (
    ProjectService,
    ProjectServiceError,
    NotFound,
)
from backend.services.users.user_service import UserService
from backend.services.organisation_service import OrganisationService
from backend.services.users.authentication_service import token_auth
from backend.services.project_admin_service import (
    ProjectAdminService,
    ProjectAdminServiceError,
    InvalidGeoJson,
    InvalidData,
)


class ProjectsRestAPI(Resource):
    @token_auth.login_required(optional=True)
    def get(self, project_id):
        """
        Get a specified project including it's area
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: false
              type: string
              default: Token sessionTokenHere==
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
            - in: query
              name: as_file
              type: boolean
              description: Set to true if file download is preferred
              default: False
            - in: query
              name: abbreviated
              type: boolean
              description: Set to true if only state information is desired
              default: False
        responses:
            200:
                description: Project found
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            authenticated_user_id = token_auth.current_user()
            as_file = (
                strtobool(request.args.get("as_file"))
                if request.args.get("as_file")
                else False
            )
            abbreviated = (
                strtobool(request.args.get("abbreviated"))
                if request.args.get("abbreviated")
                else False
            )

            project_dto = ProjectService.get_project_dto_for_mapper(
                project_id,
                authenticated_user_id,
                request.environ.get("HTTP_ACCEPT_LANGUAGE"),
                abbreviated,
            )

            if project_dto:
                project_dto = project_dto.to_primitive()
                if as_file:
                    return send_file(
                        io.BytesIO(geojson.dumps(project_dto).encode("utf-8")),
                        mimetype="application/json",
                        as_attachment=True,
                        attachment_filename=f"project_{str(project_id)}.json",
                    )

                return project_dto, 200
            else:
                return {"Error": "Private Project"}, 403
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except ProjectServiceError as e:
            return {"Error": str(e)}, 403
        except Exception as e:
            error_msg = f"Project GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch project"}, 500
        finally:
            # this will try to unlock tasks that have been locked too long
            try:
                ProjectService.auto_unlock_tasks(project_id)
            except Exception as e:
                current_app.logger.critical(str(e))

    @token_auth.login_required
    def post(self):
        """
        Creates a tasking-manager project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - in: body
              name: body
              required: true
              description: JSON object for creating draft project
              schema:
                properties:
                    cloneFromProjectId:
                        type: int
                        default: 1
                        description: Specify this value if you want to clone a project, otherwise avoid information
                    projectName:
                        type: string
                        default: HOT Project
                    areaOfInterest:
                        schema:
                            properties:
                                type:
                                    type: string
                                    default: FeatureCollection
                                features:
                                    type: array
                                    items:
                                        schema:
                                            $ref: "#/definitions/GeoJsonFeature"
                        tasks:
                            schema:
                                properties:
                                    type:
                                        type: string
                                        default: FeatureCollection
                                    features:
                                        type: array
                                        items:
                                            schema:
                                                $ref: "#/definitions/GeoJsonFeature"
                        arbitraryTasks:
                            type: boolean
                            default: false
        responses:
            201:
                description: Draft project created successfully
            400:
                description: Client Error - Invalid Request
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            500:
                description: Internal Server Error
        """
        try:
            draft_project_dto = DraftProjectDTO(request.get_json())
            draft_project_dto.user_id = token_auth.current_user()
            draft_project_dto.validate()
        except DataError as e:
            current_app.logger.error(f"error validating request: {str(e)}")
            return {"Error": "Unable to create project"}, 400

        try:
            draft_project_id = ProjectAdminService.create_draft_project(
                draft_project_dto
            )
            return {"projectId": draft_project_id}, 201
        except ProjectAdminServiceError as e:
            return {"Error": str(e)}, 403
        except (InvalidGeoJson, InvalidData):
            return {"Error": "Invalid GeoJson"}, 400
        except Exception as e:
            error_msg = f"Project PUT - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to create project"}, 500

    @token_auth.login_required
    def head(self, project_id):
        """
        Retrieves a Tasking-Manager project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Project found
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            ProjectAdminService.is_user_action_permitted_on_project(
                token_auth.current_user(), project_id
            )
        except ValueError as e:
            error_msg = f"ProjectsRestAPI HEAD: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            project_dto = ProjectAdminService.get_project_dto_for_admin(project_id)
            return project_dto.to_primitive(), 200
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except Exception as e:
            error_msg = f"ProjectsRestAPI HEAD - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch project"}, 500

    @token_auth.login_required
    def patch(self, project_id):
        """
        Updates a Tasking-Manager project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for updating an existing project
              schema:
                properties:
                    projectStatus:
                        type: string
                        default: DRAFT
                    projectPriority:
                        type: string
                        default: MEDIUM
                    defaultLocale:
                        type: string
                        default: en
                    mapperLevel:
                        type: string
                        default: BEGINNER
                    validation_permission:
                        type: string
                        default: ANY
                    mapping_permission:
                        type: string
                        default: ANY
                    private:
                        type: boolean
                        default: false
                    changesetComment:
                        type: string
                        default: hotosm-project-1
                    dueDate:
                        type: date
                        default: "2017-04-11T12:38:49"
                    imagery:
                        type: string
                        default: http//www.bing.com/maps/
                    josmPreset:
                        type: string
                        default: josm preset goes here
                    mappingTypes:
                        type: array
                        items:
                            type: string
                        default: [BUILDINGS, ROADS]
                    mappingEditors:
                        type: array
                        items:
                            type: string
                        default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS]
                    validationEditors:
                        type: array
                        items:
                            type: string
                        default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS]
                    campaign:
                        type: string
                        default: malaria
                    organisation:
                        type: integer
                        default: 1
                    countryTag:
                          type: array
                          items:
                              type: string
                          default: []
                    licenseId:
                        type: integer
                        default: 1
                        description: Id of imagery license associated with the project
                    allowedUsernames:
                        type: array
                        items:
                            type: string
                        default: ["Iain Hunter", LindaA1]
                    priorityAreas:
                        type: array
                        items:
                            schema:
                                $ref: "#/definitions/GeoJsonPolygon"
                    projectInfoLocales:
                        type: array
                        items:
                            schema:
                                $ref: "#/definitions/ProjectInfo"
                    taskCreationMode:
                        type: integer
                        default: GRID
        responses:
            200:
                description: Project updated
            400:
                description: Client Error - Invalid Request
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        authenticated_user_id = token_auth.current_user()
        try:
            ProjectAdminService.is_user_action_permitted_on_project(
                authenticated_user_id, project_id
            )
        except ValueError as e:
            error_msg = f"ProjectsRestAPI PATCH: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            project_dto = ProjectDTO(request.get_json())
            project_dto.project_id = project_id
            project_dto.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Unable to update project"}, 400

        try:
            ProjectAdminService.update_project(project_dto, authenticated_user_id)
            return {"Status": "Updated"}, 200
        except InvalidGeoJson as e:
            return {"Invalid GeoJson": str(e)}, 400
        except NotFound as e:
            return {"Error": str(e) or "Project Not Found"}, 404
        except ProjectAdminServiceError as e:
            return {"Error": str(e)}, 400
        except Exception as e:
            error_msg = f"ProjectsRestAPI PATCH - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to update project"}, 500

    @token_auth.login_required
    def delete(self, project_id):
        """
        Deletes a Tasking-Manager project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Project deleted
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            authenticated_user_id = token_auth.current_user()
            ProjectAdminService.is_user_action_permitted_on_project(
                authenticated_user_id, project_id
            )
        except ValueError as e:
            error_msg = f"ProjectsRestAPI DELETE: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            ProjectAdminService.delete_project(project_id, authenticated_user_id)
            return {"Success": "Project deleted"}, 200
        except ProjectAdminServiceError:
            return {"Error": "Project has some mapping"}, 403
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except Exception as e:
            error_msg = f"ProjectsRestAPI DELETE - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to delete project"}, 500


class ProjectSearchBase(Resource):
    @token_auth.login_required(optional=True)
    def setup_search_dto(self) -> ProjectSearchDTO:
        search_dto = ProjectSearchDTO()
        search_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
        search_dto.mapper_level = request.args.get("mapperLevel")
        search_dto.action = request.args.get("action")
        search_dto.organisation_name = request.args.get("organisationName")
        search_dto.organisation_id = request.args.get("organisationId")
        search_dto.team_id = request.args.get("teamId")
        search_dto.campaign = request.args.get("campaign")
        search_dto.order_by = request.args.get("orderBy", "priority")
        search_dto.country = request.args.get("country")
        search_dto.order_by_type = request.args.get("orderByType", "ASC")
        search_dto.page = (
            int(request.args.get("page")) if request.args.get("page") else 1
        )
        search_dto.text_search = request.args.get("textSearch")
        search_dto.omit_map_results = strtobool(
            request.args.get("omitMapResults", "false")
        )
        search_dto.last_updated_gte = request.args.get("lastUpdatedFrom")
        search_dto.last_updated_lte = request.args.get("lastUpdatedTo")
        search_dto.created_gte = request.args.get("createdFrom")
        search_dto.created_lte = request.args.get("createdTo")

        # See https://github.com/hotosm/tasking-manager/pull/922 for more info
        try:
            authenticated_user_id = token_auth.current_user()
            if request.args.get("createdByMe") == "true":
                search_dto.created_by = authenticated_user_id

            if request.args.get("mappedByMe") == "true":
                search_dto.mapped_by = authenticated_user_id

            if request.args.get("favoritedByMe") == "true":
                search_dto.favorited_by = authenticated_user_id

            if request.args.get("managedByMe") == "true":
                search_dto.managed_by = authenticated_user_id

        except Exception:
            pass

        mapping_types_str = request.args.get("mappingTypes")
        if mapping_types_str:
            search_dto.mapping_types = map(
                str, mapping_types_str.split(",")
            )  # Extract list from string
        search_dto.mapping_types_exact = strtobool(
            request.args.get("mappingTypesExact", "false")
        )
        project_statuses_str = request.args.get("projectStatuses")
        if project_statuses_str:
            search_dto.project_statuses = map(str, project_statuses_str.split(","))
        interests_str = request.args.get("interests")
        if interests_str:
            search_dto.interests = map(int, interests_str.split(","))
        search_dto.validate()

        return search_dto


class ProjectsAllAPI(ProjectSearchBase):
    @token_auth.login_required(optional=True)
    def get(self):
        """
        List and search for projects
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              type: string
              default: Token sessionTokenHere==
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
            - in: query
              name: mapperLevel
              type: string
            - in: query
              name: orderBy
              type: string
              default: priority
              enum: [id,mapper_level,priority,status,last_updated,due_date]
            - in: query
              name: orderByType
              type: string
              default: ASC
              enum: [ASC, DESC]
            - in: query
              name: mappingTypes
              type: string
            - in: query
              name: mappingTypesExact
              type: boolean
              default: false
              description: if true, limits projects to match the exact mapping types requested
            - in: query
              name: organisationName
              description: Organisation name to search for
              type: string
            - in: query
              name: organisationId
              description: Organisation ID to search for
              type: integer
            - in: query
              name: campaign
              description: Campaign name to search for
              type: string
            - in: query
              name: page
              description: Page of results user requested
              type: integer
              default: 1
            - in: query
              name: textSearch
              description: Text to search
              type: string
            - in: query
              name: country
              description: Project country
              type: string
            - in: query
              name: action
              description: Filter projects by possible actions
              enum: [map, validate, any]
              type: string
            - in: query
              name: projectStatuses
              description: Authenticated PMs can search for archived or draft statuses
              type: string
            - in: query
              name: lastUpdatedFrom
              description: Filter projects whose last update date is equal or greater than a date
              type: string
            - in: query
              name: lastUpdatedTo
              description: Filter projects whose last update date is equal or lower than a date
              type: string
            - in: query
              name: createdFrom
              description: Filter projects whose creation date is equal or greater than a date
              type: string
            - in: query
              name: createdTo
              description: Filter projects whose creation date is equal or lower than a date
              type: string
            - in: query
              name: interests
              type: string
              description: Filter by interest on project
              default: null
            - in: query
              name: createdByMe
              description: Limit to projects created by the authenticated user
              type: boolean
              default: false
            - in: query
              name: mappedByMe
              description: Limit to projects mapped/validated by the authenticated user
              type: boolean
              default: false
            - in: query
              name: favoritedByMe
              description: Limit to projects favorited by the authenticated user
              type: boolean
              default: false
            - in: query
              name: managedByMe
              description:
                Limit to projects that can be managed by the authenticated user,
                excluding the ones created by them
              type: boolean
              default: false
            - in: query
              name: teamId
              type: string
              description: Filter by team on project
              default: null
              name: omitMapResults
              type: boolean
              description: If true, it will not return the project centroid's geometries.
              default: false
        responses:
            200:
                description: Projects found
            404:
                description: No projects found
            500:
                description: Internal Server Error
        """
        try:
            user = None
            user_id = token_auth.current_user()
            if user_id:
                user = UserService.get_user_by_id(user_id)
            search_dto = self.setup_search_dto()
            results_dto = ProjectSearchService.search_projects(search_dto, user)
            return results_dto.to_primitive(), 200
        except NotFound:
            return {"mapResults": {}, "results": []}, 200
        except (KeyError, ValueError) as e:
            error_msg = f"Projects GET - {str(e)}"
            return {"Error": error_msg}, 400
        except Exception as e:
            error_msg = f"Projects GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch projects"}, 500


class ProjectsQueriesBboxAPI(Resource):
    @token_auth.login_required
    def get(self):
        """
        List and search projects by bounding box
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              default: en
            - in: query
              name: bbox
              description: comma separated list xmin, ymin, xmax, ymax
              type: string
              required: true
              default: 34.404,-1.034, 34.717,-0.624
            - in: query
              name: srid
              description: srid of bbox coords
              type: integer
              default: 4326
            - in: query
              name: createdByMe
              description: limit to projects created by authenticated user
              type: boolean
              required: true
              default: false

        responses:
            200:
                description: ok
            400:
                description: Client Error - Invalid Request
            403:
                description: Forbidden
            500:
                description: Internal Server Error
        """
        try:
            authenticated_user_id = token_auth.current_user()
            orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto(
                authenticated_user_id
            )
            if len(orgs_dto.organisations) < 1:
                raise ValueError("User not a project manager")
        except ValueError as e:
            error_msg = f"ProjectsQueriesBboxAPI GET: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            search_dto = ProjectSearchBBoxDTO()
            search_dto.bbox = map(float, request.args.get("bbox").split(","))
            search_dto.input_srid = request.args.get("srid")
            search_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            created_by_me = (
                strtobool(request.args.get("createdByMe"))
                if request.args.get("createdByMe")
                else False
            )
            if created_by_me:
                search_dto.project_author = authenticated_user_id
            search_dto.validate()
        except Exception as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Unable to fetch projects"}, 400
        try:
            geojson = ProjectSearchService.get_projects_geojson(search_dto)
            return geojson, 200
        except BBoxTooBigError:
            return {"Error": "Bounding Box too large"}, 403
        except ProjectSearchServiceError:
            return {"Error": "Unable to fetch projects"}, 400
        except Exception as e:
            error_msg = f"ProjectsQueriesBboxAPI GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch projects"}, 500


class ProjectsQueriesOwnerAPI(ProjectSearchBase):
    @token_auth.login_required
    def get(self):
        """
        Get all projects for logged in admin
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
        responses:
            200:
                description: All mapped tasks validated
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Admin has no projects
            500:
                description: Internal Server Error
        """
        try:
            authenticated_user_id = token_auth.current_user()
            orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto(
                authenticated_user_id
            )
            if len(orgs_dto.organisations) < 1:
                raise ValueError("User not a project manager")
        except ValueError as e:
            error_msg = f"ProjectsQueriesOwnerAPI GET: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            search_dto = self.setup_search_dto()
            admin_projects = ProjectAdminService.get_projects_for_admin(
                authenticated_user_id,
                request.environ.get("HTTP_ACCEPT_LANGUAGE"),
                search_dto,
            )
            return admin_projects.to_primitive(), 200
        except NotFound:
            return {"Error": "No comments found"}, 404
        except Exception as e:
            error_msg = f"Project GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": error_msg}, 500


class ProjectsQueriesTouchedAPI(Resource):
    def get(self, username):
        """
        Gets projects user has mapped
        ---
        tags:
          - projects
        produces:
          - application/json
        parameters:
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
            - name: username
              in: path
              description: The users username
              required: true
              type: string
              default: Thinkwhere
        responses:
            200:
                description: Mapped projects found
            404:
                description: User not found
            500:
                description: Internal Server Error
        """
        try:
            locale = (
                request.environ.get("HTTP_ACCEPT_LANGUAGE")
                if request.environ.get("HTTP_ACCEPT_LANGUAGE")
                else "en"
            )
            user_dto = UserService.get_mapped_projects(username, locale)
            return user_dto.to_primitive(), 200
        except NotFound:
            return {"Error": "User not found"}, 404
        except Exception as e:
            error_msg = f"User GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch projects"}, 500


class ProjectsQueriesSummaryAPI(Resource):
    def get(self, project_id: int):
        """
        Gets project summary
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
            - name: project_id
              in: path
              description: The ID of the project
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Project Summary
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            summary = ProjectService.get_project_summary(project_id, preferred_locale)
            return summary.to_primitive(), 200
        except NotFound:
            return {"Error": "Project not found"}, 404
        except Exception as e:
            error_msg = f"Project Summary GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch project summary"}, 500


class ProjectsQueriesNoGeometriesAPI(Resource):
    def get(self, project_id):
        """
        Get HOT Project for mapping
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
            - in: query
              name: as_file
              type: boolean
              description: Set to true if file download is preferred
              default: False
        responses:
            200:
                description: Project found
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            as_file = (
                strtobool(request.args.get("as_file"))
                if request.args.get("as_file")
                else False
            )
            locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            project_dto = ProjectService.get_project_dto_for_mapper(
                project_id, None, locale, True
            )
            project_dto = project_dto.to_primitive()

            if as_file:
                return send_file(
                    io.BytesIO(geojson.dumps(project_dto).encode("utf-8")),
                    mimetype="application/json",
                    as_attachment=True,
                    attachment_filename=f"project_{str(project_id)}.json",
                )

            return project_dto, 200
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except ProjectServiceError:
            return {"Error": "Unable to fetch project"}, 403
        except Exception as e:
            error_msg = f"Project GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch project"}, 500
        finally:
            # this will try to unlock tasks that have been locked too long
            try:
                ProjectService.auto_unlock_tasks(project_id)
            except Exception as e:
                current_app.logger.critical(str(e))


class ProjectsQueriesNoTasksAPI(Resource):
    @token_auth.login_required
    def get(self, project_id):
        """
        Retrieves a Tasking-Manager project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Project found
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            ProjectAdminService.is_user_action_permitted_on_project(
                token_auth.current_user(), project_id
            )
        except ValueError as e:
            error_msg = f"ProjectsQueriesNoTasksAPI GET: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            project_dto = ProjectAdminService.get_project_dto_for_admin(project_id)
            return project_dto.to_primitive(), 200
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except Exception as e:
            error_msg = f"Project GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": error_msg}, 500


class ProjectsQueriesAoiAPI(Resource):
    def get(self, project_id):
        """
        Get AOI of Project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
            - in: query
              name: as_file
              type: boolean
              description: Set to false if file download not preferred
              default: True
        responses:
            200:
                description: Project found
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            as_file = (
                strtobool(request.args.get("as_file"))
                if request.args.get("as_file")
                else True
            )

            project_aoi = ProjectService.get_project_aoi(project_id)

            if as_file:
                return send_file(
                    io.BytesIO(geojson.dumps(project_aoi).encode("utf-8")),
                    mimetype="application/json",
                    as_attachment=True,
                    attachment_filename=f"{str(project_id)}.geojson",
                )

            return project_aoi, 200
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except ProjectServiceError:
            return {"Error": "Unable to fetch project"}, 403
        except Exception as e:
            error_msg = f"Project GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch project"}, 500


class ProjectsQueriesPriorityAreasAPI(Resource):
    def get(self, project_id):
        """
        Get Priority Areas of a project
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Project found
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            priority_areas = ProjectService.get_project_priority_areas(project_id)
            return priority_areas, 200
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except ProjectServiceError:
            return {"Error": "Unable to fetch project"}, 403
        except Exception as e:
            error_msg = f"Project GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch project"}, 500


class ProjectsQueriesFeaturedAPI(Resource):
    def get(self):
        """
        Get featured projects
        ---
        tags:
            - projects
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: false
              type: string
              default: Token sessionTokenHere==
        responses:
            200:
                description: Featured projects
            500:
                description: Internal Server Error
        """
        try:
            preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            projects_dto = ProjectService.get_featured_projects(preferred_locale)
            return projects_dto.to_primitive(), 200
        except Exception as e:
            error_msg = f"FeaturedProjects GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": error_msg}, 500
