# 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.
"""Triggerer command."""

from __future__ import annotations

from collections.abc import Generator
from contextlib import contextmanager
from functools import partial
from multiprocessing import Process

from airflow.cli.commands.daemon_utils import run_command_with_daemon_option
from airflow.configuration import conf
from airflow.exceptions import AirflowConfigException
from airflow.jobs.job import Job, run_job
from airflow.jobs.triggerer_job_runner import TriggererJobRunner
from airflow.utils import cli as cli_utils
from airflow.utils.memray_utils import MemrayTraceComponents, enable_memray_trace
from airflow.utils.process_utils import set_component_mp_start_method
from airflow.utils.providers_configuration_loader import providers_configuration_loaded


@contextmanager
def _serve_logs(skip_serve_logs: bool = False) -> Generator[None, None, None]:
    """Start serve_logs sub-process."""
    from airflow.utils.serve_logs import serve_logs

    sub_proc = None
    if skip_serve_logs is False:
        port = conf.getint("logging", "trigger_log_server_port", fallback=8794)
        sub_proc = Process(target=partial(serve_logs, port=port))
        sub_proc.start()
    try:
        yield
    finally:
        if sub_proc:
            sub_proc.terminate()


@enable_memray_trace(component=MemrayTraceComponents.triggerer)
def triggerer_run(
    skip_serve_logs: bool,
    capacity: int,
    triggerer_heartrate: float,
    queues: set[str] | None = None,
    team_name: str | None = None,
):
    set_component_mp_start_method("triggerer")
    with _serve_logs(skip_serve_logs):
        triggerer_job_runner = TriggererJobRunner(
            job=Job(heartrate=triggerer_heartrate), capacity=capacity, queues=queues, team_name=team_name
        )
        run_job(job=triggerer_job_runner.job, execute_callable=triggerer_job_runner._execute)


@cli_utils.action_cli
@providers_configuration_loaded
def triggerer(args):
    """Start Airflow Triggerer."""
    from airflow.sdk._shared.secrets_masker import SecretsMasker

    SecretsMasker.enable_log_masking()

    cli_utils.print_banner()
    if args.queues and not conf.getboolean("triggerer", "queues_enabled", fallback=False):
        raise AirflowConfigException(
            "--queues option may only be used when triggerer.queues_enabled is `True`."
        )

    multi_team = conf.getboolean("core", "multi_team")
    team_name: str | None = getattr(args, "team_name", None)

    if team_name and not multi_team:
        raise AirflowConfigException("--team-name option may only be used when core.multi_team is enabled.")

    if team_name:
        from airflow.models.team import Team

        if Team.get_name_if_exists(team_name) is None:
            raise AirflowConfigException(f"Team {team_name!r} does not exist.")

    queues = set(args.queues) if args.queues else None
    triggerer_heartrate = conf.getfloat("triggerer", "JOB_HEARTBEAT_SEC")

    if cli_utils.should_enable_hot_reload(args):
        from airflow.cli.hot_reload import run_with_reloader

        run_with_reloader(
            lambda: triggerer_run(
                args.skip_serve_logs, args.capacity, triggerer_heartrate, queues, team_name
            ),
            process_name="triggerer",
        )
        return

    run_command_with_daemon_option(
        args=args,
        process_name="triggerer",
        callback=lambda: triggerer_run(
            args.skip_serve_logs, args.capacity, triggerer_heartrate, queues, team_name
        ),
        should_setup_logging=True,
    )
