#!/usr/bin/env python3
#
#  IRIS Source Code
#  Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
#  ir@cyberactionlab.net - contact@dfir-iris.org
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 3 of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

# IMPORTS ------------------------------------------------
from datetime import datetime

import marshmallow
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from flask_wtf import FlaskForm

from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_tasks_db import add_comment_to_task
from app.datamgmt.case.case_tasks_db import add_task
from app.datamgmt.case.case_tasks_db import delete_task
from app.datamgmt.case.case_tasks_db import delete_task_comment
from app.datamgmt.case.case_tasks_db import get_case_task_comment
from app.datamgmt.case.case_tasks_db import get_case_task_comments
from app.datamgmt.case.case_tasks_db import get_case_tasks_comments_count
from app.datamgmt.case.case_tasks_db import get_task
from app.datamgmt.case.case_tasks_db import get_task_with_assignees
from app.datamgmt.case.case_tasks_db import get_tasks_status
from app.datamgmt.case.case_tasks_db import get_tasks_with_assignees
from app.datamgmt.case.case_tasks_db import update_task_assignees
from app.datamgmt.case.case_tasks_db import update_task_status
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import get_tasks_state
from app.datamgmt.states import update_tasks_state
from app.forms import CaseTaskForm
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import CaseAccessLevel
from app.models.authorization import User
from app.models.models import CaseTasks
from app.schema.marshables import CaseTaskSchema
from app.schema.marshables import CommentSchema
from app.util import ac_api_case_requires
from app.util import ac_case_requires
from app.util import response_error
from app.util import response_success

case_tasks_blueprint = Blueprint('case_tasks',
                                 __name__,
                                 template_folder='templates')


# CONTENT ------------------------------------------------
@case_tasks_blueprint.route('/case/tasks', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_tasks(caseid, url_redir):
    if url_redir:
        return redirect(url_for('case_tasks.case_tasks', cid=caseid, redirect=True))

    form = FlaskForm()
    case = get_case(caseid)

    return render_template("case_tasks.html", case=case, form=form)


@case_tasks_blueprint.route('/case/tasks/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_tasks(caseid):
    ct = get_tasks_with_assignees(caseid)

    if not ct:
        output = []
    else:
        output = ct

    ret = {
        "tasks_status": get_tasks_status(),
        "tasks": output,
        "state": get_tasks_state(caseid=caseid)
    }

    return response_success("", data=ret)


@case_tasks_blueprint.route('/case/tasks/state', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_tasks_state(caseid):
    os = get_tasks_state(caseid=caseid)
    if os:
        return response_success(data=os)
    else:
        return response_error('No tasks state for this case.')


@case_tasks_blueprint.route('/case/tasks/status/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_task_statusupdate(cur_id, caseid):
    task = get_task(task_id=cur_id, caseid=caseid)
    if not task:
        return response_error("Invalid task ID for this case")

    if request.is_json:

        if update_task_status(request.json.get('task_status_id'), cur_id, caseid):
            task_schema = CaseTaskSchema()

            return response_success("Task status updated", data=task_schema.dump(task))
        else:
            return response_error("Invalid status")

    else:
        return response_error("Invalid request")


@case_tasks_blueprint.route('/case/tasks/add/modal', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_task_modal(caseid):

    task = CaseTasks()
    task.custom_attributes = get_default_custom_attributes('task')
    form = CaseTaskForm()
    form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
    form.task_assignees_id.choices = []

    return render_template("modal_add_case_task.html", form=form, task=task, uid=current_user.id, user_name=None,
                           attributes=task.custom_attributes)


@case_tasks_blueprint.route('/case/tasks/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_task(caseid):
    try:
        # validate before saving
        task_schema = CaseTaskSchema()
        request_data = call_modules_hook('on_preload_task_create', data=request.get_json(), caseid=caseid)

        if 'task_assignee_id' in request_data or 'task_assignees_id' not in request_data:
            return response_error('task_assignee_id is not valid anymore since v1.5.0')

        task_assignee_list = request_data['task_assignees_id']
        del request_data['task_assignees_id']
        task = task_schema.load(request_data)

        ctask = add_task(task=task,
                         assignee_id_list=task_assignee_list,
                         user_id=current_user.id,
                         caseid=caseid
                         )

        ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=caseid)

        if ctask:
            track_activity(f"added task \"{ctask.task_title}\"", caseid=caseid)
            return response_success("Task '{}' added".format(ctask.task_title), data=task_schema.dump(ctask))

        return response_error("Unable to create task for internal reasons")

    except marshmallow.exceptions.ValidationError as e:
        return response_error(msg="Data error", data=e.messages, status=400)


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_task_view(cur_id, caseid):
    task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
    if not task:
        return response_error("Invalid task ID for this case")

    task_schema = CaseTaskSchema()

    return response_success(data=task_schema.dump(task))


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_task_view_modal(cur_id, caseid, url_redir):
    if url_redir:
        return redirect(url_for('case_tasks.case_tasks', cid=caseid, redirect=True))

    form = CaseTaskForm()

    task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
    form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
    form.task_assignees_id.choices = []

    if not task:
        return response_error("Invalid task ID for this case")

    form.task_title.render_kw = {'value': task.task_title}
    form.task_description.data = task.task_description
    user_name, = User.query.with_entities(User.name).filter(User.id == task.task_userid_update).first()
    comments_map = get_case_tasks_comments_count([task.id])

    return render_template("modal_add_case_task.html", form=form, task=task, user_name=user_name,
                           comments_map=comments_map, attributes=task.custom_attributes)


@case_tasks_blueprint.route('/case/tasks/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_edit_task(cur_id, caseid):
    try:
        task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
        if not task:
            return response_error("Invalid task ID for this case")

        request_data = call_modules_hook('on_preload_task_update', data=request.get_json(), caseid=caseid)

        if 'task_assignee_id' in request_data or 'task_assignees_id' not in request_data:
            return response_error('task_assignee_id is not valid anymore since v1.5.0')

        # validate before saving
        task_assignee_list = request_data['task_assignees_id']
        del request_data['task_assignees_id']
        task_schema = CaseTaskSchema()

        request_data['id'] = cur_id
        task = task_schema.load(request_data, instance=task)

        task.task_userid_update = current_user.id
        task.task_last_update = datetime.utcnow()

        update_task_assignees(task, task_assignee_list, caseid)

        update_tasks_state(caseid=caseid)

        db.session.commit()

        task = call_modules_hook('on_postload_task_update', data=task, caseid=caseid)

        if task:
            track_activity(f"updated task \"{task.task_title}\" (status {task.task_status_id})",
                           caseid=caseid)
            return response_success("Task '{}' updated".format(task.task_title), data=task_schema.dump(task))

        return response_error("Unable to update task for internal reasons")

    except marshmallow.exceptions.ValidationError as e:
        return response_error(msg="Data error", data=e.messages, status=400)


@case_tasks_blueprint.route('/case/tasks/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_delete_task(cur_id, caseid):
    call_modules_hook('on_preload_task_delete', data=cur_id, caseid=caseid)
    task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
    if not task:
        return response_error("Invalid task ID for this case")

    delete_task(task.id)

    update_tasks_state(caseid=caseid)

    call_modules_hook('on_postload_task_delete', data=cur_id, caseid=caseid)

    track_activity(f"deleted task \"{task.task_title}\"")

    return response_success("Task deleted")


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_task_modal(cur_id, caseid, url_redir):
    if url_redir:
        return redirect(url_for('case_task.case_task', cid=caseid, redirect=True))

    task = get_task(cur_id, caseid=caseid)
    if not task:
        return response_error('Invalid task ID')

    return render_template("modal_conversation.html", element_id=cur_id, element_type='tasks',
                           title=task.task_title)


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_task_list(cur_id, caseid):

    task_comments = get_case_task_comments(cur_id)
    if task_comments is None:
        return response_error('Invalid task ID')

    return response_success(data=CommentSchema(many=True).dump(task_comments))


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_task_add(cur_id, caseid):

    try:
        task = get_task(cur_id, caseid=caseid)
        if not task:
            return response_error('Invalid task ID')

        comment_schema = CommentSchema()

        comment = comment_schema.load(request.get_json())
        comment.comment_case_id = caseid
        comment.comment_user_id = current_user.id
        comment.comment_date = datetime.now()
        comment.comment_update_date = datetime.now()
        db.session.add(comment)
        db.session.commit()

        add_comment_to_task(task.id, comment.comment_id)

        db.session.commit()

        hook_data = {
            "comment": comment_schema.dump(comment),
            "task": CaseTaskSchema().dump(task)
        }
        call_modules_hook('on_postload_task_commented', data=hook_data, caseid=caseid)

        track_activity(f"task \"{task.task_title}\" commented", caseid=caseid)
        return response_success("Task commented", data=comment_schema.dump(comment))

    except marshmallow.exceptions.ValidationError as e:
        return response_error(msg="Data error", data=e.normalized_messages(), status=400)


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_task_get(cur_id, com_id, caseid):

    comment = get_case_task_comment(cur_id, com_id)
    if not comment:
        return response_error("Invalid comment ID")

    return response_success(data=comment._asdict())


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_task_edit(cur_id, com_id, caseid):

    return case_comment_update(com_id, 'tasks', caseid)


@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_task_delete(cur_id, com_id, caseid):

    success, msg = delete_task_comment(cur_id, com_id)
    if not success:
        return response_error(msg)

    call_modules_hook('on_postload_task_comment_delete', data=com_id, caseid=caseid)

    track_activity(f"comment {com_id} on task {cur_id} deleted", caseid=caseid)
    return response_success(msg)

