import json
from typing import List, Dict
from pathlib import Path
import os

from flask import redirect, url_for, Blueprint, send_file, render_template
from werkzeug.utils import secure_filename
import redis  # dependency via rq
import uuid

import api_process_findings
import config
from helpers.file_utils import json_list_to_csv
from helpers.flask_response import basic_msg, basic_msg_html, response_as_download
from helpers.rq_helper import REDIS_QUEUE, rq_job_failed, rq_job_finalized
from helpers.custom_logging import FlashLog
from pwndoc_api import download_report, get_docx_filepath, does_audit_exist, get_findings_from_audit, \
    download_and_return_audit
from template_manager import PwndocTemplateManager
from template_converters import PwndocConverter
from tqdm import tqdm


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


@bp.route("/", methods=["GET"])
def audit_index():
    audits_dict = api_process_findings.get_audits_with_nice_names(include_file_formats=False)
    return render_template('audit_index.html', audit_dict=audits_dict)


@bp.route("/report_download/<audit_id>", methods=["GET"])
def report_download(audit_id: str):
    docx_filename = f"report_{audit_id}_{uuid.uuid4()}.docx"

    if config.RQ_DISABLED:
        download_report(audit_id, docx_filename)
        return redirect(url_for("api_pwndoc_audit.report_progress", docx_filename=docx_filename))

    try:
        enqueue_result = REDIS_QUEUE.enqueue(
            download_report, args=(audit_id, docx_filename,),
            job_timeout=config.RQ_JOB_TIMEOUT, ttl='1d', result_ttl=7*24*60*60, job_id=docx_filename
        )
    except redis.exceptions.ConnectionError as e:
        FlashLog.log_and_flash_with_level(f"Failed to connect to Redis - is the container running? ({e})", "error")

    return redirect(url_for("api_pwndoc_audit.report_progress", docx_filename=docx_filename))


@bp.route("/report_progress/<docx_filename>", methods=["GET"])
def report_progress(docx_filename: str):
    docx_filename = secure_filename(docx_filename)
    result_filepath = get_docx_filepath(docx_filename)

    def _success_response():
        return basic_msg_html(f'Report file is ready. <a href="{url_for("api_pwndoc_audit.report_file", docx_filename=docx_filename)}">Download docx report</a>')

    does_file_exist = Path(result_filepath).is_file()

    if config.RQ_DISABLED:
        if does_file_exist:
            return _success_response()
        FlashLog.error("The download of report failed.")
        return basic_msg("")

    if rq_job_finalized(docx_filename) is False:
        return basic_msg_html("""
            This pwndoc docx result doesn't not exist yet. This page will autorefresh until it does.
            <script> setTimeout(function () { location.reload(1); }, 2000); </script>
            """)

    if rq_job_failed(docx_filename):
        FlashLog.error("The download of report in background failed.")
        return basic_msg("")

    if not does_file_exist:
        FlashLog.flash_loguru_msg("The download of report in background is no longer running, but no result file was produced. This likely indicates error.", "danger")
        return basic_msg("")

    return _success_response()


@bp.route("/report_file/<docx_filename>", methods=["GET"])
def report_file(docx_filename: str):
    result_filepath = get_docx_filepath(secure_filename(docx_filename))
    return send_file(result_filepath, download_name=os.path.basename(result_filepath), as_attachment=True)


@bp.route("/export_raw_audit/<audit_id>", methods=["GET"])
def export_audit_raw(audit_id: str):
    if not does_audit_exist(audit_id):
        return basic_msg(f"Audit with this ID doesn't exist."), 400

    audit_data = download_and_return_audit(audit_id)
    json_result = json.dumps(audit_data, indent=4)
    return response_as_download(json_result, f'{secure_filename(audit_data["name"])}.json', 'json')


def convert_findings_to_scan2report_dicts(audit_id: str, locale: str) -> List[dict]:
    findings = get_findings_from_audit(audit_id)
    findings_scan2report_dicts: List[Dict] = []

    for single_finding_raw in tqdm(findings):
        single_finding = PwndocTemplateManager.parse_single_pwndoc_locale(single_finding_raw)
        single_finding.set_is_template(False)
        single_finding.locale = locale

        single_finding_scan2report_raw = PwndocConverter.convert_to_single_scan2report_dict(single_finding)
        findings_scan2report_dicts.append(single_finding_scan2report_raw)

    return findings_scan2report_dicts


@bp.route("/export_findings/<audit_id>/<file_format>", methods=["GET"])
def export_findings(audit_id: str, file_format: str):
    SUPPORTED_FILE_FORMATS = ['json', 'csv']
    if file_format not in SUPPORTED_FILE_FORMATS:
        return basic_msg(f"Unsupported output format. Supported ones are: {SUPPORTED_FILE_FORMATS}"), 400

    if not does_audit_exist(audit_id):
        return basic_msg(f"Audit with this ID doesn't exist."), 400

    audit_data = download_and_return_audit(audit_id)
    locale: str = audit_data['language']
    audit_name: str = audit_data['name']

    findings_scan2report_dicts = convert_findings_to_scan2report_dicts(audit_id, locale)

    if file_format == 'json':
        json_result = json.dumps(findings_scan2report_dicts, indent=4)
        return response_as_download(json_result, f'{secure_filename(audit_name)}.json', 'json')

    if file_format == 'csv':
        csv_result = json_list_to_csv(findings_scan2report_dicts)
        return response_as_download(csv_result, f'{secure_filename(audit_name)}.csv', 'csv')

    return 'This file format is not implemented', 501
