# -*- coding: utf-8 -*-
import datetime
import json
import logging
import os.path
import shutil
from operator import itemgetter

from cairosvg import svg2png
from flask import (
    Blueprint,
    abort,
    current_app,
    flash,
    jsonify,
    redirect,
    render_template,
    request,
    send_file,
    send_from_directory,
    url_for,
)
from flask_login import current_user

from scout.constants import CUSTOM_CASE_REPORTS
from scout.server.extensions import mail, store
from scout.server.utils import (
    html_to_pdf_file,
    institute_and_case,
    jsonconverter,
    templated,
    user_institutes,
    zip_dir_to_obj,
)

from . import controllers

LOG = logging.getLogger(__name__)

cases_bp = Blueprint(
    "cases",
    __name__,
    template_folder="templates",
    static_folder="static",
    static_url_path="/cases/static",
)


@cases_bp.route("/institutes")
@templated("cases/index.html")
def index():
    """Display a list of all user institutes."""
    institute_objs = user_institutes(store, current_user)
    institutes_count = (
        (institute_obj, sum(1 for i in store.cases(collaborator=institute_obj["_id"])))
        for institute_obj in institute_objs
        if institute_obj
    )
    return dict(institutes=institutes_count)


@cases_bp.route("/<institute_id>/<case_name>")
@templated("cases/case.html")
def case(institute_id, case_name):
    """Display one case."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    if not case_obj:
        flash("Case {} does not exist in database!".format(case_name))
        return redirect(request.referrer)

    data = controllers.case(store, institute_obj, case_obj)

    return dict(
        **data,
    )


@cases_bp.route("/<institute_id>/<case_name>/sma", methods=["GET"])
@templated("cases/case_sma.html")
def sma(institute_id, case_name):
    """Visualize case SMA data - SMN CN calls"""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    data = controllers.case(store, institute_obj, case_obj)
    return dict(format="html", **data)


@cases_bp.route("/beacon_submit", methods=["POST"])
def beacon_submit():
    """Submit case variants to Beacon"""

    controllers.beacon_add(request.form)
    return redirect(request.referrer)


@cases_bp.route("/beacon_remove/<case_id>", methods=["GET"])
def beacon_remove(case_id):
    """Remove all variants from a case from Beacon"""

    controllers.beacon_remove(case_id)
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/mme_matches", methods=["GET", "POST"])
@templated("cases/matchmaker.html")
def matchmaker_matches(institute_id, case_name):
    """Show all MatchMaker matches for a given case"""

    data = controllers.matchmaker_matches(request, institute_id, case_name)
    return data


@cases_bp.route("/<institute_id>/<case_name>/mme_match/<target>", methods=["GET", "POST"])
def matchmaker_match(institute_id, case_name, target):
    """Starts an internal match or a match against one or all MME external nodes"""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    match_results = controllers.matchmaker_match(request, target, institute_id, case_name)
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/mme_add", methods=["POST"])
def matchmaker_add(institute_id, case_name):
    """Add or update a case in MatchMaker"""
    # Call matchmaker_delete controller to add a patient to MatchMaker
    controllers.matchmaker_add(request, institute_id, case_name)
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/mme_delete", methods=["POST"])
def matchmaker_delete(institute_id, case_name):
    """Remove a case from MatchMaker"""
    # Call matchmaker_delete controller to delete a patient from MatchMaker
    controllers.matchmaker_delete(request, institute_id, case_name)
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/individuals", methods=["POST"])
def update_individual(institute_id, case_name):
    """Update individual data (age and/or Tissue type) for a case"""

    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    user_obj = store.user(current_user.email)
    ind_id = request.form.get("update_ind")
    age = request.form.get("_".join(["age", ind_id]))
    tissue = request.form.get("_".join(["tissue", ind_id]))
    controllers.update_individuals(
        store=store,
        institute_obj=institute_obj,
        case_obj=case_obj,
        user_obj=user_obj,
        ind=ind_id,
        age=age,
        tissue=tissue,
    )
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/samples", methods=["POST"])
def update_cancer_sample(institute_id, case_name):
    """Update cancer sample-associated data: tumor purity, tissue type, tumor type"""

    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    user_obj = store.user(current_user.email)
    ind_id = request.form.get("update_ind")

    tumor_type = request.form.get(".".join(["tumor_type", ind_id]))
    tissue_type = request.form.get(".".join(["tissue_type", ind_id]))
    tumor_purity = request.form.get(".".join(["tumor_purity", ind_id]))

    controllers.update_cancer_samples(
        store=store,
        institute_obj=institute_obj,
        case_obj=case_obj,
        user_obj=user_obj,
        ind=ind_id,
        tissue=tissue_type,
        tumor_type=tumor_type,
        tumor_purity=tumor_purity,
    )
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/synopsis", methods=["POST"])
def case_synopsis(institute_id, case_name):
    """Update (PUT) synopsis of a specific case."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    user_obj = store.user(current_user.email)
    new_synopsis = request.form.get("synopsis")
    controllers.update_synopsis(store, institute_obj, case_obj, user_obj, new_synopsis)
    return redirect(request.referrer)


@cases_bp.route("/api/v1/<institute_id>/<case_name>/case_report", methods=["GET"])
def api_case_report(institute_id, case_name):
    """API endpoint that returns case report json data"""
    data = controllers.case_report_content(store, institute_id, case_name)
    json_data = json.dumps({"data": data}, default=jsonconverter)
    return json_data


@cases_bp.route("/<institute_id>/<case_name>/case_report", methods=["GET"])
@templated("cases/case_report.html")
def case_report(institute_id, case_name):
    """Visualize case report"""
    data = controllers.case_report_content(store, institute_id, case_name)
    return dict(format="html", **data)


@cases_bp.route("/<institute_id>/<case_name>/pdf_report", methods=["GET"])
def pdf_case_report(institute_id, case_name):
    """Download a pdf report for a case"""

    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    data = controllers.case_report_content(store, institute_id, case_name)

    # add coverage report on the bottom of this report
    if (
        current_app.config.get("SQLALCHEMY_DATABASE_URI")
        and case_obj.get("track", "rare") != "cancer"
    ):
        data["coverage_report"] = controllers.coverage_report_contents(
            request.url_root, institute_obj, case_obj
        )

    # Workaround to be able to print the case pedigree to pdf
    if case_obj.get("madeline_info") and case_obj.get("madeline_info") != "":
        write_to = os.path.join(cases_bp.static_folder, "madeline.png")
        svg2png(
            bytestring=case_obj["madeline_info"],
            write_to=write_to,
        )  # Transform to png, since PDFkit can't render svg images
        data["case"]["madeline_path"] = write_to

    html_report = render_template("cases/case_report.html", format="pdf", **data)

    bytes_file = html_to_pdf_file(
        html_string=html_report, orientation="portrait", dpi=300, zoom=0.6
    )
    file_name = "_".join(
        [
            case_obj["display_name"],
            datetime.datetime.now().strftime("%Y-%m-%d"),
            "scout_report.pdf",
        ]
    )
    return send_file(
        bytes_file,
        attachment_filename=file_name,
        mimetype="application/pdf",
        as_attachment=True,
    )


@cases_bp.route("/<institute_id>/<case_name>/mt_report", methods=["GET"])
def mt_report(institute_id, case_name):
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)

    # create a temp folder to write excel files into
    temp_excel_dir = os.path.join(cases_bp.static_folder, "_".join([case_name, "mt_reports"]))
    os.makedirs(temp_excel_dir, exist_ok=True)

    if controllers.mt_excel_files(store, case_obj, temp_excel_dir):
        data = zip_dir_to_obj(temp_excel_dir)

        shutil.rmtree(temp_excel_dir)

        today = datetime.datetime.now().strftime("%Y-%m-%d")
        return send_file(
            data,
            mimetype="application/zip",
            as_attachment=True,
            attachment_filename="_".join(["scout", case_name, "MT_report", today]) + ".zip",
            cache_timeout=0,
        )

    shutil.rmtree(temp_excel_dir)

    flash("No MT report excel file could be exported for this sample", "warning")
    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/diagnose", methods=["POST"])
def case_diagnosis(institute_id, case_name):
    """Add or remove a diagnosis for a case."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    user_obj = store.user(current_user.email)
    link = url_for(".case", institute_id=institute_id, case_name=case_name)

    omim_id = request.form["omim_term"].split("|")[0]
    omim_inds = request.form.getlist("omim_inds")  # Individual-level phenotypes

    if not "OMIM:" in omim_id:  # Could be an omim number provided by user
        omim_id = ":".join(["OMIM", omim_id])

    # Make sure omim term exists in database:
    omim_obj = store.disease_term(omim_id.strip())
    if omim_obj is None:
        flash("Couldn't find any disease term with id: {}".format(omim_id), "warning")
        return redirect(request.referrer)

    remove = True if request.args.get("remove") == "yes" else False
    store.diagnose(institute_obj, case_obj, user_obj, link, omim_obj, omim_inds, remove)
    return redirect("#".join([link, "omim_assign"]))


@cases_bp.route("/<institute_id>/<case_name>/phenotypes", methods=["POST"])
@cases_bp.route("/<institute_id>/<case_name>/phenotypes/<phenotype_id>", methods=["POST"])
def phenotypes(institute_id, case_name, phenotype_id=None):
    """Handle phenotypes."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    case_url = url_for(".case", institute_id=institute_id, case_name=case_name)
    is_group = request.args.get("is_group") == "yes"
    user_obj = store.user(current_user.email)

    if phenotype_id:
        # DELETE a phenotype item/group from case
        store.remove_phenotype(
            institute_obj, case_obj, user_obj, case_url, phenotype_id, is_group=is_group
        )
    else:
        try:
            # add a new phenotype item/group to the case
            hpo_term = None
            omim_term = None

            phenotype_term = request.form["hpo_term"]
            phenotype_inds = request.form.getlist("phenotype_inds")  # Individual-level phenotypes

            if phenotype_term.startswith("HP:") or len(phenotype_term) == 7:
                hpo_term = phenotype_term.split(" | ", 1)[0]
            else:
                omim_term = phenotype_term

            store.add_phenotype(
                institute=institute_obj,
                case=case_obj,
                user=user_obj,
                link=case_url,
                hpo_term=hpo_term,
                omim_term=omim_term,
                is_group=is_group,
                phenotype_inds=phenotype_inds,
            )
        except ValueError:
            flash(
                f"Unable to add phenotype for the given terms:{phenotype_term}",
                "warning",
            )
            return redirect(case_url)

    return redirect("#".join([case_url, "phenotypes_panel"]))


def parse_raw_gene_ids(raw_symbols):
    """Parse raw gene symbols for hgnc_symbols from web form autocompletion.

    Arguments:
        raw_symbol_strings(list(string)) - formated "17284 | POT1 (hPot1, POT1)"

    Returns:
        hgnc_ids(set(int))
    """
    hgnc_ids = set()

    for raw_symbol in raw_symbols:
        LOG.debug("raw gene: {}".format(raw_symbol))
        # avoid empty lists
        if raw_symbol:
            # take the first nubmer before |, and remove any space.
            try:
                hgnc_ids.add(int(raw_symbol.split("|", 1)[0].replace(" ", "")))
            except ValueError:
                flash(
                    "Provided gene info could not be parsed! "
                    "Please allow autocompletion to finish.",
                    "warning",
                )

    LOG.debug("Parsed HGNC symbols {}".format(hgnc_ids))

    return hgnc_ids


def parse_raw_gene_symbols(raw_symbols_list):
    """Parse list of concatenated gene symbol list for hgnc_symbols from Phenomizer.

    Arguments:
        raw_symbols(list(string)) - e.g. ("POT1 | MUTYH", "POT1 | ATXN1 | ATXN7")

    Returns:
        hgnc_symbols(set(string)) - set of (unique) gene symbols without intervening chars
    """
    hgnc_symbols = set()

    for raw_symbols in raw_symbols_list:
        LOG.debug("raw gene: {}".format(raw_symbols))
        # avoid empty lists
        if raw_symbols:
            hgnc_symbols.update(
                raw_symbol.split(" ", 1)[0] for raw_symbol in raw_symbols.split("|")
            )
    LOG.debug("Parsed HGNC symbols {}".format(hgnc_symbols))

    return hgnc_symbols


@cases_bp.route("/<institute_id>/<case_name>/phenotypes/actions", methods=["POST"])
def phenotypes_actions(institute_id, case_name):
    """Perform actions on multiple phenotypes."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    case_url = url_for(".case", institute_id=institute_id, case_name=case_name)
    action = request.form["action"]
    hpo_ids = request.form.getlist("hpo_id")
    user_obj = store.user(current_user.email)

    if action == "PHENOMIZER":
        if len(hpo_ids) == 0:
            hpo_ids = [term["phenotype_id"] for term in case_obj.get("phenotype_terms", [])]

        username = current_app.config["PHENOMIZER_USERNAME"]
        password = current_app.config["PHENOMIZER_PASSWORD"]
        diseases = controllers.hpo_diseases(username, password, hpo_ids)
        if diseases:
            return render_template(
                "cases/diseases.html",
                diseases=diseases,
                institute=institute_obj,
                case=case_obj,
            )

    if action == "DELETE":
        for hpo_id in hpo_ids:
            # DELETE a phenotype from the list
            store.remove_phenotype(institute_obj, case_obj, user_obj, case_url, hpo_id)

    if action == "ADDGENE":
        hgnc_ids = parse_raw_gene_ids(request.form.getlist("genes"))
        store.update_dynamic_gene_list(case_obj, hgnc_ids=list(hgnc_ids), add_only=True)

    if action == "GENES":
        hgnc_symbols = parse_raw_gene_symbols(request.form.getlist("genes"))
        store.update_dynamic_gene_list(case_obj, hgnc_symbols=list(hgnc_symbols))

    if action == "GENERATE":
        if len(hpo_ids) == 0:
            hpo_ids = [term["phenotype_id"] for term in case_obj.get("phenotype_terms", [])]
        results = store.generate_hpo_gene_list(*hpo_ids)
        # determine how many HPO terms each gene must match
        hpo_count = int(request.form.get("min_match") or 1)
        hgnc_ids = [result[0] for result in results if result[1] >= hpo_count]
        store.update_dynamic_gene_list(case_obj, hgnc_ids=hgnc_ids, phenotype_ids=hpo_ids)

    return redirect("#".join([case_url, "phenotypes_panel"]))


@cases_bp.route("/<institute_id>/<case_name>/events", methods=["POST"])
@cases_bp.route("/<institute_id>/<case_name>/events/<event_id>", methods=["POST"])
def events(institute_id, case_name, event_id=None):
    """Handle events."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    link = request.form.get("link")
    content = request.form.get("content")
    variant_id = request.args.get("variant_id")
    user_obj = store.user(current_user.email)
    if event_id:
        if "remove" in request.form:
            # delete the event
            store.delete_event(event_id)
        elif "edit" in request.form:
            # edit comment
            store.update_comment(
                comment_id=event_id,
                new_content=request.form.get("updatedContent"),
                level=request.form.get("level", "specific"),
            )
    else:
        if variant_id:
            # create a variant comment
            variant_obj = store.variant(variant_id)
            level = request.form.get("level", "specific")
            store.comment(
                institute_obj,
                case_obj,
                user_obj,
                link,
                variant=variant_obj,
                content=content,
                comment_level=level,
            )
        else:
            # create a case comment
            store.comment(institute_obj, case_obj, user_obj, link, content=content)

    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/status", methods=["POST"])
def status(institute_id, case_name):
    """Update status of a specific case."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    user_obj = store.user(current_user.email)

    status = request.form.get("status", case_obj["status"])
    link = url_for(".case", institute_id=institute_id, case_name=case_name)

    if status == "archived":
        store.archive_case(institute_obj, case_obj, user_obj, link)
    else:
        store.update_status(institute_obj, case_obj, user_obj, status, link)

    return redirect(request.referrer)


@cases_bp.route("/<institute_id>/<case_name>/assign", methods=["POST"])
@cases_bp.route("/<institute_id>/<case_name>/<user_id>/<inactivate>/assign", methods=["POST"])
def assign(institute_id, case_name, user_id=None, inactivate=False):
    """Assign and unassign a user from a case."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    link = url_for(".case", institute_id=institute_id, case_name=case_name)
    if user_id:
        user_obj = store.user(user_id)
    else:
        user_obj = store.user(current_user.email)
    if request.form.get("action") == "DELETE":
        store.unassign(institute_obj, case_obj, user_obj, link, inactivate)
    else:
        store.assign(institute_obj, case_obj, user_obj, link)
    return redirect(request.referrer)


@cases_bp.route("/api/v1/<institute_id>/cases")
def caselist(institute_id):
    """Search for cases for autocompletion"""
    query = request.args.get("query")
    if query is None:
        return abort(500)
    display_names = sorted(
        [
            case["display_name"]
            for case in store.cases(owner=institute_id, name_query="case:" + query)
        ]
    )
    json_terms = [{"name": "{}".format(display_name)} for display_name in display_names]

    return jsonify(json_terms)


@cases_bp.route("/api/v1/hpo-terms")
def hpoterms():
    """Search for HPO terms."""
    query = request.args.get("query")
    if query is None:
        return abort(500)
    terms = sorted(store.hpo_terms(query=query), key=itemgetter("hpo_number"))
    json_terms = [
        {"name": "{} | {}".format(term["_id"], term["description"]), "id": term["_id"]}
        for term in terms[:7]
    ]

    return jsonify(json_terms)


@cases_bp.route("/api/v1/omim-terms")
def omimterms():
    query = request.args.get("query")
    if query is None:
        return abort(500)
    terms = store.query_omim(query=query)
    json_terms = [
        {"name": "{} | {}".format(term["_id"], term["description"]), "id": term["_id"]}
        for term in terms[:7]
    ]
    return jsonify(json_terms)


@cases_bp.route("/<institute_id>/<case_name>/<variant_id>/pin", methods=["POST"])
def pin_variant(institute_id, case_name, variant_id):
    """Pin and unpin variants to/from the list of suspects."""
    institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
    variant_obj = store.variant(variant_id)
    user_obj = store.user(current_user.email)
    link = url_for(
        "variant.variant",
        institute_id=institute_id,
        case_name=case_name,
        variant_id=variant_id,
    )
    if request.form["action"] == "ADD":