from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError

from backend.models.dtos.grid_dto import SplitTaskDTO
from backend.models.postgis.utils import NotFound, InvalidGeoJson
from backend.services.grid.split_service import SplitService, SplitServiceError
from backend.services.users.user_service import UserService
from backend.services.project_admin_service import ProjectAdminService
from backend.services.users.authentication_service import token_auth, tm
from backend.models.dtos.validator_dto import (
    LockForValidationDTO,
    UnlockAfterValidationDTO,
    StopValidationDTO,
)
from backend.services.validator_service import (
    ValidatorService,
    ValidatorServiceError,
    UserLicenseError,
)
from backend.models.dtos.mapping_dto import (
    LockTaskDTO,
    StopMappingTaskDTO,
    MappedTaskDTO,
)
from backend.services.mapping_service import MappingService, MappingServiceError


class TasksActionsMappingLockAPI(Resource):
    @token_auth.login_required
    def post(self, project_id, task_id):
        """
        Locks a task for mapping
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - name: task_id
              in: path
              description: Unique task ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Task locked
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            409:
                description: User has not accepted license terms of project
            500:
                description: Internal Server Error
        """
        try:
            lock_task_dto = LockTaskDTO()
            lock_task_dto.user_id = token_auth.current_user()
            lock_task_dto.project_id = project_id
            lock_task_dto.task_id = task_id
            lock_task_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            lock_task_dto.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Unable to lock task"}, 400

        try:
            task = MappingService.lock_task_for_mapping(lock_task_dto)
            return task.to_primitive(), 200
        except NotFound:
            return {"Error": "Task Not Found"}, 404
        except MappingServiceError as e:
            return {"Error": str(e)}, 403
        except UserLicenseError:
            return {"Error": "User not accepted license terms"}, 409
        except Exception as e:
            error_msg = f"Task Lock API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to lock task"}, 500


class TasksActionsMappingStopAPI(Resource):
    @token_auth.login_required
    def post(self, project_id, task_id):
        """
        Unlock a task that is locked for mapping resetting it to its last status
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - name: task_id
              in: path
              description: Unique task ID
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for unlocking a task
              schema:
                  id: TaskUpdateStop
                  properties:
                      comment:
                          type: string
                          description: Optional user comment about the task
                          default: Comment about mapping done before stop
        responses:
            200:
                description: Task unlocked
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            500:
                description: Internal Server Error
        """
        try:
            stop_task = StopMappingTaskDTO(request.get_json())
            stop_task.user_id = token_auth.current_user()
            stop_task.task_id = task_id
            stop_task.project_id = project_id
            stop_task.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            stop_task.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Task unlock failed"}, 400

        try:
            task = MappingService.stop_mapping_task(stop_task)
            return task.to_primitive(), 200
        except NotFound:
            return {"Error": "Task Not Found"}, 404
        except MappingServiceError:
            return {"Error": "Task unlock failed"}, 403
        except Exception as e:
            error_msg = f"Task Lock API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Task unlock failed"}, 500


class TasksActionsMappingUnlockAPI(Resource):
    @token_auth.login_required
    def post(self, project_id, task_id):
        """
        Set a task as mapped
        ---
        tags:
            - tasks
        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: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - name: task_id
              in: path
              description: Unique task ID
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for unlocking a task
              schema:
                  id: TaskUpdateUnlock
                  required:
                      - status
                  properties:
                      status:
                          type: string
                          description: The new status for the task
                          default: MAPPED
                      comment:
                          type: string
                          description: Optional user comment about the task
                          default: Comment about the mapping
        responses:
            200:
                description: Task unlocked
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            500:
                description: Internal Server Error
        """
        try:
            authenticated_user_id = token_auth.current_user()
            mapped_task = MappedTaskDTO(request.get_json())
            mapped_task.user_id = authenticated_user_id
            mapped_task.task_id = task_id
            mapped_task.project_id = project_id
            mapped_task.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Task unlock failed"}, 400

        try:
            task = MappingService.unlock_task_after_mapping(mapped_task)
            return task.to_primitive(), 200
        except NotFound:
            return {"Error": "Task Not Found"}, 404
        except MappingServiceError:
            return {"Error": "Task unlock failed"}, 403
        except Exception as e:
            error_msg = f"Task Lock API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Task unlock failed"}, 500
        finally:
            # Refresh mapper level after mapping
            UserService.check_and_update_mapper_level(authenticated_user_id)


class TasksActionsMappingUndoAPI(Resource):
    @token_auth.login_required
    def post(self, project_id, task_id):
        """
        Undo a task's mapping status
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - name: task_id
              in: path
              description: Unique task ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Task found
            403:
                description: Forbidden
            404:
                description: Task not found
            500:
                description: Internal Server Error
        """
        try:
            preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            task = MappingService.undo_mapping(
                project_id, task_id, token_auth.current_user(), preferred_locale
            )
            return task.to_primitive(), 200
        except NotFound:
            return {"Error": "Task Not Found"}, 404
        except MappingServiceError:
            return {"Error": "User not permitted to undo task"}, 403
        except Exception as e:
            error_msg = f"Task GET API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to lock task"}, 500


class TasksActionsValidationLockAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Lock tasks for validation
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the tasks are associated with
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for locking task(s)
              schema:
                  properties:
                      taskIds:
                          type: array
                          items:
                              type: integer
                          description: Array of taskIds for locking
                          default: [1,2]
        responses:
            200:
                description: Task(s) locked for validation
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            409:
                description: User has not accepted license terms of project
            500:
                description: Internal Server Error
        """
        try:
            validator_dto = LockForValidationDTO(request.get_json())
            validator_dto.project_id = project_id
            validator_dto.user_id = token_auth.current_user()
            validator_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            validator_dto.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Unable to lock task"}, 400

        try:
            tasks = ValidatorService.lock_tasks_for_validation(validator_dto)
            return tasks.to_primitive(), 200
        except ValidatorServiceError as e:
            error_msg = f"Validator Lock API - {str(e)}"
            return {"Error": error_msg}, 403
        except NotFound:
            return {"Error": "Task not found"}, 404
        except UserLicenseError:
            return {"Error": "User not accepted license terms"}, 409
        except Exception as e:
            error_msg = f"Validator Lock API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to lock task"}, 500


class TasksActionsValidationStopAPI(Resource):
    @tm.pm_only(False)
    @token_auth.login_required
    def post(self, project_id):
        """
        Unlock tasks that are locked for validation resetting them to their last status
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for unlocking a task
              schema:
                  properties:
                      resetTasks:
                          type: array
                          items:
                              schema:
                                  $ref: "#/definitions/ResetTask"
        responses:
            200:
                description: Task unlocked
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            500:
                description: Internal Server Error
        """
        try:
            validated_dto = StopValidationDTO(request.get_json())
            validated_dto.project_id = project_id
            validated_dto.user_id = token_auth.current_user()
            validated_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            validated_dto.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Task unlock failed"}, 400

        try:
            tasks = ValidatorService.stop_validating_tasks(validated_dto)
            return tasks.to_primitive(), 200
        except ValidatorServiceError:
            return {"Error": "Task unlock failed"}, 403
        except NotFound:
            return {"Error": "Task unlock failed"}, 404
        except Exception as e:
            error_msg = f"Stop Validating API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Task unlock failed"}, 500


class TasksActionsValidationUnlockAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Set tasks as validated
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for unlocking a task
              schema:
                  properties:
                      validatedTasks:
                          type: array
                          items:
                              schema:
                                  $ref: "#/definitions/ValidatedTask"
        responses:
            200:
                description: Task unlocked
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            500:
                description: Internal Server Error
        """
        try:
            validated_dto = UnlockAfterValidationDTO(request.get_json())
            validated_dto.project_id = project_id
            validated_dto.user_id = token_auth.current_user()
            validated_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE")
            validated_dto.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Task unlock failed"}, 400

        try:
            tasks = ValidatorService.unlock_tasks_after_validation(validated_dto)
            return tasks.to_primitive(), 200
        except ValidatorServiceError:
            return {"Error": "Task unlock failed"}, 403
        except NotFound:
            return {"Error": "Task unlock failed"}, 404
        except Exception as e:
            error_msg = f"Validator Lock API - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Task unlock failed"}, 500


class TasksActionsMapAllAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Map all tasks on a project
        ---
        tags:
            - tasks
        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: All tasks mapped
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            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"TasksActionsMapAllAPI POST: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            MappingService.map_all_tasks(project_id, authenticated_user_id)
            return {"Success": "All tasks mapped"}, 200
        except Exception as e:
            error_msg = f"TasksActionsMapAllAPI POST - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to map all the tasks"}, 500


class TasksActionsValidateAllAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Validate all mapped tasks on a project
        ---
        tags:
            - tasks
        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: All mapped tasks validated
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            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"TasksActionsValidateAllAPI POST: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            ValidatorService.validate_all_tasks(project_id, authenticated_user_id)
            return {"Success": "All tasks validated"}, 200
        except Exception as e:
            error_msg = f"TasksActionsValidateAllAPI POST - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to validate all tasks"}, 500


class TasksActionsInvalidateAllAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Invalidate all mapped tasks on a project
        ---
        tags:
            - tasks
        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: All mapped tasks invalidated
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            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"TasksActionsInvalidateAllAPI POST: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            ValidatorService.invalidate_all_tasks(project_id, authenticated_user_id)
            return {"Success": "All tasks invalidated"}, 200
        except Exception as e:
            error_msg = f"TasksActionsInvalidateAllAPI POST - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to invalidate all tasks"}, 500


class TasksActionsResetBadImageryAllAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Set all bad imagery tasks as ready for mapping
        ---
        tags:
            - tasks
        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: All bad imagery tasks marked ready for mapping
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            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"TasksActionsResetBadImageryAllAPI POST: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            MappingService.reset_all_badimagery(project_id, authenticated_user_id)
            return {"Success": "All bad imagery tasks marked ready for mapping"}, 200
        except Exception as e:
            error_msg = (
                f"TasksActionsResetBadImageryAllAPI POST - unhandled error: {str(e)}"
            )
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to reset tasks"}, 500


class TasksActionsResetAllAPI(Resource):
    @token_auth.login_required
    def post(self, project_id):
        """
        Reset all tasks on project back to ready, preserving history
        ---
        tags:
            - tasks
        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: All tasks reset
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            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"TasksActionsResetAllAPI POST: {str(e)}"
            return {"Error": error_msg}, 403

        try:
            ProjectAdminService.reset_all_tasks(project_id, authenticated_user_id)
            return {"Success": "All tasks reset"}, 200
        except Exception as e:
            error_msg = f"TasksActionsResetAllAPI POST - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to reset tasks"}, 500


class TasksActionsSplitAPI(Resource):
    @token_auth.login_required
    def post(self, project_id, task_id):
        """
        Split a task
        ---
        tags:
            - tasks
        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
            - name: project_id
              in: path
              description: Project ID the task is associated with
              required: true
              type: integer
              default: 1
            - name: task_id
              in: path
              description: Unique task ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Task split OK
            400:
                description: Client Error
            401:
                description: Unauthorized - Invalid credentials
            403:
                description: Forbidden
            404:
                description: Task not found
            500:
                description: Internal Server Error
        """
        try:
            split_task_dto = SplitTaskDTO()
            split_task_dto.user_id = token_auth.current_user()
            split_task_dto.project_id = project_id
            split_task_dto.task_id = task_id
            split_task_dto.preferred_locale = request.environ.get(
                "HTTP_ACCEPT_LANGUAGE"
            )
            split_task_dto.validate()
        except DataError as e:
            current_app.logger.error(f"Error validating request: {str(e)}")
            return {"Error": "Unable to split task"}, 400
        try:
            tasks = SplitService.split_task(split_task_dto)
            return tasks.to_primitive(), 200
        except NotFound:
            return {"Error": "Task Not Found"}, 404
        except SplitServiceError as e:
            return {"Error": str(e)}, 403
        except InvalidGeoJson as e:
            return {"Error": str(e)}, 500
        except Exception as e:
            error_msg = f"TasksActionsSplitAPI POST - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to split task"}, 500
