import datetime
import os
from typing import List, Tuple
import time

from tqdm import tqdm

from flask import Blueprint, render_template, Response, send_file, url_for

from helpers.pwndoc_log_beautify import log_to_beautified_errors
from helpers.template_grouping import TemplateAliasing, TemplateGrouping
from helpers.custom_logging import FlashLog
import pwndoc_db_init
from helpers.file_utils import zip_multiple_folders_non_recursive_to_virtual, relative_path
from template_manager import PwndocTemplateManager
import template_pwndoc
from pwndoc_api import login, refresh_examples, delete_all_templates_in_pwndoc, load_audit_file, \
    get_findings_from_audit, download_and_return_finding, delete_finding, get_audits
from helpers.db_models import DbTemplate
from template_pwndoc import TNSTemplatePwndoc, get_fid_from_raw_finding
from template_scan2report import TemplateScan2Report
from template_converters import PwndocConverter

bp = Blueprint('api_debug', __name__, template_folder='templates')


@bp.route("/", methods=["GET"])
def show_debug_actions_list():
    audits_dict = get_audits()
    return render_template('debug_actions.html', audits_dict=audits_dict)


@bp.route("/download_api", methods=["GET"])
def download_api_examples():
    login()
    refresh_examples()
    return "API examples downloaded"


@bp.route("/force_upload_init_data", methods=["GET"])
def force_upload_init_data():
    pwndoc_db_init.InitialData(force=True)
    return "API inited"


@bp.route("/refresh_api_examples", methods=["GET"])
def debug_refresh_api_examples():
    login()
    refresh_examples()
    return f"OK"


@bp.route("/download_logs/<log_name>", methods=["GET"])
def debug_download_logs(log_name: str = "main"):
    log_folder_paths = {
        'importer': 'user_data/importer-logs/',
        'pwndoc': 'user_data/pwndoc-logs/',
        'worker': 'user_data/worker-logs/',
    }

    if log_name in ["main", "gunicorn_error", "gunicorn_access", "everything"]:
        folder_path = log_folder_paths['importer']
    elif log_name in ["pwndoc"]:
        folder_path = log_folder_paths['pwndoc']
    else:
        return "This log doesn't exist or it's download is not permitted.", 400

    # todo: everything currently does not include Pwndoc
    if log_name != "everything":
        with open(f"{folder_path}{log_name}.log", encoding="utf8") as f:
            return Response(f.read(), mimetype='application/json')

    beautified_log_path = os.path.join(log_folder_paths['importer'], f'pwndoc_beautified_{int(time.time())}.log')
    with open(beautified_log_path, 'w', encoding='utf8') as f:
        beautified_text = generate_pwndoc_human_readable_log()
        f.write(beautified_text)

    memory_file = zip_multiple_folders_non_recursive_to_virtual(
        list(log_folder_paths.values())
     )

    return send_file(
        memory_file,
        download_name=f"pwndoc_importer_logs_{time.time()}.zip",
        as_attachment=True
    )


def generate_pwndoc_human_readable_log():
    folder_path = "user_data/pwndoc-logs/"
    log_path = relative_path(f"{folder_path}/pwndoc.log")
    with open(log_path, encoding="utf8") as f:
        log_lines = f.readlines()
    beautified_errors = log_to_beautified_errors(log_lines)

    text_answer = "This log is persistent - it shows all errors since the logs creation. That means some of the errors might have already been resolved."
    text_answer += "\nThe newest logs are on the top. Only errors which could be beautified are displayed here."
    text_answer += f"\nGenerated {str(datetime.datetime.now())} \n\n==================\n\n"

    text_answer += "\n\n================== NEXT ERROR ==================\n\n".join(beautified_errors)
    return text_answer


@bp.route("/pwndoc_human_readable_log", methods=["GET"])
def make_pwndoc_human_readable_log():
    text_answer = generate_pwndoc_human_readable_log()

    raw_response = Response(text_answer)
    raw_response.mimetype = 'text/plain'
    return raw_response


@bp.route("/test_db", methods=["GET"])
def debug_test_db():
    template1 = template_pwndoc.TNSTemplatePwndoc(fid="fid1", locale="cs")
    template2 = template_pwndoc.TNSTemplatePwndoc(fid="fid1", locale="og")

    DbTemplate.delete_template("fid")

    assert DbTemplate.get_template("fid", "og") is None

    res2, _ = DbTemplate.add_template("fid", "og", template1)
    assert DbTemplate.get_template("fid", "og") == res2

    DbTemplate.delete_template("fid")
    assert DbTemplate.get_template("fid", "og") is None

    res5, _ = DbTemplate.add_template("fid", "og", template1)
    res6, _ = DbTemplate.add_template("fid", "og", template2)
    assert res6.get_previous_revision() == res5

    DbTemplate.delete_template("fid")
    assert DbTemplate.get_template("fid", "og", include_deleted=True) == res6
    return "OK"


@bp.route("/get_all_templates_from_db")
def debug_get_all_templates():
    current_templates: List[DbTemplate] = DbTemplate.get_all_current_templates()
    return f"{len(current_templates)} templates"


@bp.route("/delete_vulnerability_templates")
def delete_vulnerability_templates():
    dict_of_all_templates = PwndocTemplateManager.get_all_templates_as_dict_of_locales()
    for fid in dict_of_all_templates.keys():
        DbTemplate.delete_template(fid)
    delete_all_templates_in_pwndoc()
    TemplateScan2Report.delete_template_files()
    return "Deleted"


@bp.route("/force_unescape_old_templates", methods=["GET"])
def force_unescape_old_templates():
    PwndocTemplateManager.update_template_db_from_pwndoc()
    all_templates: List[TNSTemplatePwndoc] = PwndocTemplateManager.get_all_templates()

    new_templates: List[TNSTemplatePwndoc] = []
    for single_template in tqdm(all_templates, desc="Conversion to HTML"):
        tmp = PwndocConverter.html_conversion(single_template, html_to_md=False, html_force_unescape_basic_tags=True)
        # print(single_template.description)
        # print(tmp.description)
        new_templates.append(tmp)

    n_templates_changed: int = 0

    for single_template in tqdm(new_templates, desc="Adding new templates"):
        new = PwndocTemplateManager.add_single_locale_template(single_template)
        if new:
            n_templates_changed += 1

    return f"{len(new_templates)} templates processed, {n_templates_changed} templates were updated."


@bp.route("/flash_msg", methods=["GET"])
def test_flash_msg():
    FlashLog.info("test info")
    FlashLog.warning("test warning")
    FlashLog.error("test error")
    return render_template("base.html")


def _list_newly_grouped_or_aliased_findings(audit_id: str) -> List[Tuple[str, str, str]]:
    login()
    refresh_examples(audit_id)
    TemplateAliasing.refresh()
    TemplateGrouping.refresh()

    audit_data = load_audit_file(audit_id)

    newly_grouped_or_aliased_findings: List[Tuple[str, str, str]] = []

    findings = audit_data.get("datas", {}).get("findings", [])
    for single_finding in findings:
        fid = get_fid_from_raw_finding(single_finding)
        pwndoc_id = single_finding.get("_id", "")
        if fid is None:
            FlashLog.warning(f"Pwndoc finding {pwndoc_id} doesn't have fid. Skipping.")
            continue

        for qry_function in [TemplateAliasing.get_alias, TemplateGrouping.get_gid]:
            # noinspection PyArgumentList
            alias_or_gid = qry_function(fid)
            if alias_or_gid:
                newly_grouped_or_aliased_findings.append((fid, alias_or_gid, pwndoc_id))

    return newly_grouped_or_aliased_findings


@bp.route("/list_newly_grouped_or_aliased_findings/<audit_id>", methods=["GET"])
def show_newly_grouped_or_aliased_findings(audit_id: str):
    newly_grouped_or_aliased_findings = _list_newly_grouped_or_aliased_findings(audit_id)
    return "The following findings from the audit are currently part of alias or group: <br>" + \
           "FID - alias or GID - PwnDoC ID <br>" + \
           "<br>".join(["  -  ".join(x) for x in newly_grouped_or_aliased_findings]) + \
           f'<br><br><a href="{url_for("api_debug.delete_newly_grouped_or_aliased_findings", audit_id=audit_id)}"> Delete all these findings (DANGEROUS; no confirmation dialog)</a><br>'


@bp.route("/delete_newly_grouped_or_aliased_findings/<audit_id>", methods=["GET"])
def delete_newly_grouped_or_aliased_findings(audit_id: str):
    newly_grouped_or_aliased_findings = _list_newly_grouped_or_aliased_findings(audit_id)
    for _, _, pwndoc_id in newly_grouped_or_aliased_findings:
        delete_finding(audit_id, pwndoc_id)

    return "Deleted the following findings: <br>" + \
           "FID - alias or GID - PwnDoC ID <br>" + \
           "<br>".join(["  -  ".join(x) for x in newly_grouped_or_aliased_findings])


@bp.route("/download_audit/<audit_id>", methods=["GET"])
def debug_download_audit(audit_id: str):
    findings = get_findings_from_audit(audit_id)
    for single_finding in findings:
        download_and_return_finding(audit_id, single_finding.get("_id"))
    return "OK"
