import io
from uuid import UUID

from flask import abort, current_app, flash, redirect, render_template, send_file, url_for
from flask.typing import ResponseReturnValue
from werkzeug.utils import secure_filename

from app.access_grant_funding.forms import DeclineSignOffForm
from app.access_grant_funding.routes import access_grant_funding_blueprint
from app.common.auth.authorisation_helper import AuthorisationHelper
from app.common.auth.decorators import has_access_grant_role
from app.common.data.interfaces.collections import get_all_submissions_with_mode_for_collection, get_collection
from app.common.data.interfaces.grant_recipients import get_grant_recipient
from app.common.data.interfaces.user import get_current_user
from app.common.data.types import CollectionType, RoleEnum, SubmissionStatusEnum
from app.common.exceptions import SubmissionValidationFailed
from app.common.forms import GenericSubmitForm
from app.common.helpers.collections import CollectionHelper, SubmissionHelper
from app.common.helpers.feature_flags import FeatureFlags
from app.common.helpers.pdf import render_pdf
from app.extensions import auto_commit_after_request
from app.metrics import MetricEventName, emit_metric_count
from app.types import FlashMessageType


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/reports", methods=["GET"]
)
@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/forms", methods=["GET"]
)
@has_access_grant_role(RoleEnum.MEMBER)
def list_collections(organisation_id: UUID, grant_id: UUID) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    user = get_current_user()

    # TODO refactor when we persist the collection status and/or implement multiple rounds
    submissions = []
    collection_helpers = []

    monitoring_reports = grant_recipient.grant.get_access_reports_for_user(
        user, user_organisation=grant_recipient.organisation
    )
    pre_award_forms = grant_recipient.grant.get_access_pre_award_forms_for_user(
        user, user_organisation=grant_recipient.organisation
    )
    for collection in monitoring_reports + pre_award_forms:
        collection_helpers.append(CollectionHelper(collection=collection))
        submissions.extend(
            [
                SubmissionHelper(submission=submission)
                for submission in get_all_submissions_with_mode_for_collection(
                    collection_id=collection.id,
                    submission_mode=grant_recipient.submission_mode,
                    grant_recipient_ids=[grant_recipient.id],
                )
            ]
        )

    template = (
        "access_grant_funding/list_forms.html"
        if FeatureFlags.PRE_AWARD.is_enabled
        else "access_grant_funding/collection_list.html"
    )
    return render_template(
        template,
        monitoring_reports=monitoring_reports,
        pre_award_forms=pre_award_forms,
        organisation_id=organisation_id,
        grant=grant_recipient.grant,
        submissions=submissions,
        collection_helpers=collection_helpers,
        grant_recipient=grant_recipient,
        SubmissionHelper=SubmissionHelper,
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/collection/<uuid:collection_id>/submissions",
    methods=["GET"],
)
@has_access_grant_role(RoleEnum.MEMBER)
def list_collection_submissions(organisation_id: UUID, grant_id: UUID, collection_id: UUID) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    user = get_current_user()
    collection = get_collection(collection_id, grant_id=grant_id)
    if not collection.allow_multiple_submissions:
        abort(404)

    submission_helpers = [
        SubmissionHelper(submission=submission)
        for submission in get_all_submissions_with_mode_for_collection(
            collection_id=collection_id,
            submission_mode=grant_recipient.submission_mode,
            grant_recipient_ids=[grant_recipient.id],
        )
    ]

    return render_template(
        "access_grant_funding/submission_list.html",
        collection=collection,
        grant_recipient=grant_recipient,
        submission_helpers=submission_helpers,
        can_create_submissions=(
            not collection.multiple_submissions_are_managed_by_service
            and AuthorisationHelper.is_access_grant_data_provider(grant_recipient, user)
            and not CollectionHelper(collection).is_closed
        ),
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/collection/<uuid:collection_id>/unavailable",
    methods=["GET"],
)
@has_access_grant_role(RoleEnum.MEMBER)
def collection_unavailable(organisation_id: UUID, grant_id: UUID, collection_id: UUID) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    collection = get_collection(collection_id, grant_id=grant_id, with_full_schema=True)

    if not CollectionHelper(collection).has_missing_referenced_data_for_organisation(
        grant_recipient.organisation.external_id
    ):
        return redirect(
            url_for(
                "access_grant_funding.route_to_submission",
                organisation_id=organisation_id,
                grant_id=grant_id,
                collection_id=collection_id,
            )
        )

    user = get_current_user()
    emit_metric_count(
        MetricEventName.COLLECTION_BLOCKED_BY_MISSING_DATA, grant_recipient=grant_recipient, collection=collection
    )
    current_app.logger.error(
        "User %(user_id)s from grant recipient %(grant_recipient_id)s was shown the collection-unavailable page for "
        "collection %(collection_id)s because a referenced grant recipient data source is missing data",
        dict(user_id=user.id, grant_recipient_id=grant_recipient.id, collection_id=collection.id),
    )

    return render_template(
        "access_grant_funding/collection_unavailable.html", grant_recipient=grant_recipient, collection=collection
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/<collection_type:collection_type>/<uuid:submission_id>/view",
    methods=["GET", "POST"],
)
@has_access_grant_role(RoleEnum.MEMBER)
def view_locked_submission(
    organisation_id: UUID, grant_id: UUID, collection_type: CollectionType, submission_id: UUID
) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)

    submission = SubmissionHelper.load(submission_id=submission_id, grant_recipient_id=grant_recipient.id)

    if not submission.in_answers_locked_state:
        # note we're not redirecting to the route to submission as you might have been directed from
        # there, go somewhere we know will load consistently and the user can step back in
        return redirect(
            url_for("access_grant_funding.list_collections", organisation_id=organisation_id, grant_id=grant_id)
        )

    form = GenericSubmitForm()

    if form.validate_on_submit():
        return redirect(
            url_for(
                "access_grant_funding.confirm_submission_with_certify",
                organisation_id=organisation_id,
                grant_id=grant_id,
                collection_type=collection_type,
                submission_id=submission.id,
            )
        )

    return render_template(
        "access_grant_funding/view_locked_submission.html",
        grant_recipient=grant_recipient,
        submission=submission,
        form=form,
        interpolate=SubmissionHelper.get_interpolator(collection=submission.collection, submission_helper=submission),
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/<collection_type:collection_type>/<uuid:submission_id>/export-pdf",
    methods=["GET"],
)
@has_access_grant_role(RoleEnum.MEMBER)
def export_submission_pdf(
    organisation_id: UUID, grant_id: UUID, collection_type: CollectionType, submission_id: UUID
) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)

    submission = SubmissionHelper.load(submission_id=submission_id, grant_recipient_id=grant_recipient.id)

    html_content = render_template(
        "common/submission_print_baseline.html",
        grant_recipient=grant_recipient,
        submission=submission,
        interpolate=SubmissionHelper.get_interpolator(collection=submission.collection, submission_helper=submission),
    )

    emit_metric_count(MetricEventName.SUBMISSION_PDF_DOWNLOADED, submission=submission.submission)

    return send_file(
        io.BytesIO(render_pdf(html_content)),
        mimetype="application/pdf",
        as_attachment=True,
        download_name=secure_filename(f"{submission.collection.grant.name} - {submission.long_collection_name}.pdf"),
        max_age=0,
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/<collection_type:collection_type>/<uuid:submission_id>/decline",
    methods=["GET", "POST"],
)
@has_access_grant_role(RoleEnum.CERTIFIER)
@auto_commit_after_request
def decline_submission(
    organisation_id: UUID,
    grant_id: UUID,
    collection_type: CollectionType,
    submission_id: UUID,
) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    submission_helper = SubmissionHelper.load(submission_id=submission_id, grant_recipient_id=grant_recipient.id)
    user = get_current_user()

    if not submission_helper.is_awaiting_sign_off:
        current_app.logger.warning(
            "Decline certification loaded incorrectly by %(user_id)s for submission %(submission_id)s",
            extra={"user_id": user.id, "submission_id": submission_id},
        )
        return redirect(
            url_for(
                "access_grant_funding.view_locked_submission",
                organisation_id=organisation_id,
                grant_id=grant_id,
                collection_type=collection_type,
                submission_id=submission_id,
            )
        )

    if submission_helper.in_immutable_state:
        return redirect(
            url_for(
                "access_grant_funding.view_locked_submission",
                organisation_id=organisation_id,
                grant_id=grant_id,
                collection_type=collection_type,
                submission_id=submission_id,
            )
        )

    form = DeclineSignOffForm()
    if form.validate_on_submit():
        declined_reason = form.decline_reason.data or ""
        certifier_user = get_current_user()
        submission_helper.decline_certification(certifier_user, declined_reason=declined_reason)

        flash(
            {  # ty: ignore[invalid-argument-type]
                "collection_name": submission_helper.long_collection_name,
                "grant_name": submission_helper.grant.name,
                "sent_for_certification_by": submission_helper.sent_for_certification_by.name
                if submission_helper.sent_for_certification_by
                else "the submitter",
                "collection_id": submission_helper.collection.id,
                "collection_type_singular": submission_helper.collection.type.constants.singular,
            },
            FlashMessageType.SUBMISSION_SIGN_OFF_DECLINED,
        )
        return redirect(
            url_for("access_grant_funding.list_collections", organisation_id=organisation_id, grant_id=grant_id)
        )

    return render_template(
        "access_grant_funding/decline_submission.html",
        submission=submission_helper,
        grant_recipient=grant_recipient,
        form=form,
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/<collection_type:collection_type>/<uuid:submission_id>/confirm-submission-certify",
    methods=["GET", "POST"],
)
@has_access_grant_role(RoleEnum.CERTIFIER)
@auto_commit_after_request
def confirm_submission_with_certify(
    organisation_id: UUID, grant_id: UUID, collection_type: CollectionType, submission_id: UUID
) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    submission_helper = SubmissionHelper.load(submission_id=submission_id, grant_recipient_id=grant_recipient.id)
    user = get_current_user()

    if not submission_helper.is_awaiting_sign_off:
        current_app.logger.warning(
            "Confirm certify and submit loaded incorrectly by %(user_id)s for submission %(submission_id)s",
            extra={"user_id": user.id, "submission_id": submission_id},
        )
        return redirect(
            url_for(
                "access_grant_funding.view_locked_submission",
                organisation_id=organisation_id,
                grant_id=grant_id,
                collection_type=collection_type,
                submission_id=submission_id,
            )
        )

    if submission_helper.in_immutable_state:
        return redirect(
            url_for(
                "access_grant_funding.view_locked_submission",
                organisation_id=organisation_id,
                grant_id=grant_id,
                collection_type=collection_type,
                submission_id=submission_id,
            )
        )

    form = GenericSubmitForm()

    if form.validate_on_submit():
        try:
            if submission_helper.collection.requires_certification:
                submission_helper.certify(user)
            submission_helper.submit(user)

            return redirect(
                url_for(
                    "access_grant_funding.submitted_confirmation",
                    organisation_id=organisation_id,
                    grant_id=grant_id,
                    collection_type=collection_type,
                    submission_id=submission_id,
                )
            )
        except SubmissionValidationFailed as e:
            flash(e.error_message, FlashMessageType.SUBMISSION_VALIDATION_ERROR)
            return redirect(
                url_for(
                    "access_grant_funding.route_to_submission",
                    organisation_id=organisation_id,
                    grant_id=grant_id,
                    collection_id=submission_helper.collection_id,
                )
            )

    return render_template(
        "access_grant_funding/collections/submit.html",
        grant_recipient=grant_recipient,
        submission_helper=submission_helper,
        form=form,
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/<collection_type:collection_type>/<uuid:submission_id>/confirm-submission",
    methods=["GET", "POST"],
)
@has_access_grant_role(RoleEnum.DATA_PROVIDER)
@auto_commit_after_request
def confirm_submission_direct_submission(
    organisation_id: UUID, grant_id: UUID, collection_type: CollectionType, submission_id: UUID
) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    submission_helper = SubmissionHelper.load(submission_id=submission_id, grant_recipient_id=grant_recipient.id)
    user = get_current_user()

    if not submission_helper.status == SubmissionStatusEnum.READY_TO_SUBMIT:
        current_app.logger.warning(
            "Confirm submit loaded incorrectly by %(user_id)s for submission %(submission_id)s",
            extra={"user_id": user.id, "submission_id": submission_id},
        )
        return redirect(
            url_for("access_grant_funding.list_collections", organisation_id=organisation_id, grant_id=grant_id)
        )

    form = GenericSubmitForm()

    if form.validate_on_submit():
        try:
            submission_helper.submit(user)

            return redirect(
                url_for(
                    "access_grant_funding.submitted_confirmation",
                    organisation_id=organisation_id,
                    grant_id=grant_id,
                    collection_type=collection_type,
                    submission_id=submission_id,
                )
            )
        except SubmissionValidationFailed as e:
            flash(e.error_message, FlashMessageType.SUBMISSION_VALIDATION_ERROR)
            return redirect(
                url_for(
                    "access_grant_funding.route_to_submission",
                    organisation_id=organisation_id,
                    grant_id=grant_id,
                    collection_id=submission_helper.collection_id,
                )
            )

    return render_template(
        "access_grant_funding/collections/submit.html",
        grant_recipient=grant_recipient,
        submission_helper=submission_helper,
        form=form,
    )


@access_grant_funding_blueprint.route(
    "/organisation/<uuid:organisation_id>/grants/<uuid:grant_id>/<collection_type:collection_type>/<uuid:submission_id>/submitted-confirmation",
    methods=["GET"],
)
@has_access_grant_role(RoleEnum.MEMBER)
def submitted_confirmation(
    organisation_id: UUID, grant_id: UUID, collection_type: CollectionType, submission_id: UUID
) -> ResponseReturnValue:
    grant_recipient = get_grant_recipient(grant_id, organisation_id)
    submission_helper = SubmissionHelper.load(submission_id=submission_id, grant_recipient_id=grant_recipient.id)
    user = get_current_user()

    if (
        not submission_helper.is_submitted
        or (
            submission_helper.collection.requires_certification
            and not AuthorisationHelper.is_access_grant_certifier(grant_recipient, user)
        )
        or (
            not submission_helper.collection.requires_certification
            and not AuthorisationHelper.is_access_grant_data_provider(grant_recipient, user)
        )
    ):
        # note we're not redirecting to the route to submission as you might have been directed from
        # there, go somewhere we know will load consistently and the user can step back in
        return redirect(
            url_for("access_grant_funding.list_collections", organisation_id=organisation_id, grant_id=grant_id)
        )

    return render_template(
        "access_grant_funding/collections/submitted_confirmation.html",
        grant_recipient=grant_recipient,
        submission_helper=submission_helper,
    )
