# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

import json
import os

import quart
import werkzeug.wrappers.response as response
import wtforms

import atr.db as db
import atr.db.models as models
import atr.routes as routes
import atr.routes.compose as compose
import atr.routes.finish as finish
import atr.routes.vote as vote
import atr.util as util
from atr.tasks import message


class ResolveForm(util.QuartFormTyped):
    """Form for resolving a vote on a release candidate."""

    candidate_name = wtforms.StringField(
        "Candidate name", validators=[wtforms.validators.InputRequired("Candidate name is required")]
    )
    vote_result = wtforms.RadioField(
        "Vote result",
        choices=[("passed", "Passed"), ("failed", "Failed")],
        validators=[wtforms.validators.InputRequired("Vote result is required")],
    )
    resolution_body = wtforms.TextAreaField("Resolution email body", validators=[wtforms.validators.Optional()])
    submit = wtforms.SubmitField("Resolve vote")


def release_latest_vote_task(release: models.Release) -> models.Task | None:
    # Find the most recent VOTE_INITIATE task for this release
    # TODO: Make this a proper query
    for task in sorted(release.tasks, key=lambda t: t.added, reverse=True):
        if task.task_type != models.TaskType.VOTE_INITIATE:
            continue
        # if task.status != models.TaskStatus.COMPLETED:
        #     continue
        if (task.status == models.TaskStatus.QUEUED) or (task.status == models.TaskStatus.ACTIVE):
            continue
        if task.result is None:
            continue
        return task
    return None


@routes.committer("/resolve/<project_name>/<version_name>", methods=["POST"], measure_performance=False)
async def selected_post(
    session: routes.CommitterSession, project_name: str, version_name: str
) -> response.Response | str:
    """Resolve the vote on a release candidate."""
    await session.check_access(project_name)

    form = await ResolveForm.create_form(data=await quart.request.form)
    if not await form.validate_on_submit():
        for _field, errors in form.errors.items():
            for error in errors:
                await quart.flash(f"{error}", "error")
        return await session.redirect(vote.selected, project_name=project_name, version_name=version_name)

    candidate_name = form.candidate_name.data
    vote_result = form.vote_result.data
    resolution_body = util.unwrap_type(form.resolution_body.data, str)
    if not candidate_name:
        return await session.redirect(
            vote.selected, error="Missing candidate name", project_name=project_name, version_name=version_name
        )

    # Extract project name
    try:
        project_name, version_name = candidate_name.rsplit("-", 1)
    except ValueError:
        return await session.redirect(
            vote.selected, error="Invalid candidate name format", project_name=project_name, version_name=version_name
        )

    # Check that the user has access to the project
    await session.check_access(project_name)

    # Update release status in the database
    async with db.session() as data:
        async with data.begin():
            release = await session.release(
                project_name,
                version_name,
                with_tasks=True,
                with_project=True,
                phase=models.ReleasePhase.RELEASE_CANDIDATE,
                data=data,
            )

            # Update the release phase based on vote result
            if vote_result == "passed":
                release.stage = models.ReleaseStage.RELEASE
                release.phase = models.ReleasePhase.RELEASE_PREVIEW
                success_message = "Vote marked as passed"
                destination = finish.selected
            else:
                release.phase = models.ReleasePhase.RELEASE_CANDIDATE_DRAFT
                success_message = "Vote marked as failed"
                destination = compose.selected

    error_message = await _send_resolution(session, release, vote_result, resolution_body)
    if error_message is not None:
        await quart.flash(error_message, "error")

    return await session.redirect(
        destination, success=success_message, project_name=project_name, version_name=release.version
    )


def task_mid_get(latest_vote_task: models.Task) -> str | None:
    if "LOCAL_DEBUG" in os.environ:
        return "818a44a3-6984-4aba-a650-834e86780b43@apache.org"
    # TODO: Improve this
    task_mid = None

    try:
        for result in latest_vote_task.result or []:
            if isinstance(result, str):
                parsed_result = json.loads(result)
            else:
                # Shouldn't happen
                parsed_result = result
            if isinstance(parsed_result, dict):
                task_mid = parsed_result.get("mid", "(mid not found in result)")
                break
            else:
                task_mid = "(malformed result)"

    except (json.JSONDecodeError, TypeError):
        task_mid = "(malformed result)"

    return task_mid


def _format_artifact_name(project_name: str, version: str, is_podling: bool = False) -> str:
    """Format an artifact name according to Apache naming conventions.

    For regular projects: apache-${project}-${version}
    For podlings: apache-${project}-incubating-${version}
    """
    # TODO: Format this better based on committee and project
    # Must depend on whether project is a subproject or not
    if is_podling:
        return f"apache-{project_name}-incubating-{version}"
    return f"apache-{project_name}-{version}"


async def _send_resolution(
    session: routes.CommitterSession,
    release: models.Release,
    resolution: str,
    body: str,
) -> str | None:
    # Get the email thread
    latest_vote_task = release_latest_vote_task(release)
    if latest_vote_task is None:
        return "No vote task found, unable to send resolution message."
    vote_thread_mid = task_mid_get(latest_vote_task)
    if vote_thread_mid is None:
        return "No vote thread found, unable to send resolution message."

    # Construct the reply email
    # original_subject = latest_vote_task.task_args["subject"]

    # Arguments for the task to cast a vote
    email_recipient = latest_vote_task.task_args["email_to"]
    email_sender = f"{session.uid}@apache.org"
    subject = f"[VOTE] [RESULT] Release {release.project.display_name} {release.version} {resolution.upper()}"
    body = f"{body}\n\n-- \n{session.fullname} ({session.uid})"
    in_reply_to = vote_thread_mid

    task = models.Task(
        status=models.TaskStatus.QUEUED,
        task_type=models.TaskType.MESSAGE_SEND,
        task_args=message.Send(
            email_sender=email_sender,
            email_recipient=email_recipient,
            subject=subject,
            body=body,
            in_reply_to=in_reply_to,
        ).model_dump(),
        release_name=release.name,
    )
    async with db.session() as data:
        data.add(task)
        await data.flush()
        await data.commit()
    return None
