"""Hooks for circusd, used during deployment"""

import multiprocessing
import os
import socket
import time
from typing import Tuple


def _get_pg_host_port() -> Tuple[str, int]:
    import django

    if "DJANGO_SETTINGS_MODULE" not in os.environ:
        raise ValueError("The DJANGO_SETTINGS_MODULE environment variable must be set")
    # get settings with USE_PGBOUNCER=1
    os.environ["USE_PGBOUNCER"] = "1"
    django.setup()
    from django.conf import settings

    db_host = settings.DATABASES["default"]["HOST"]
    db_port = int(settings.DATABASES["default"]["PORT"])
    return db_host, db_port


def get_pg_host_port() -> Tuple[str, int]:
    """Get the host and port for the Postgres database.

    Runs in a separate process using multiprocessing so that Django setup
    doesn't pollute the globals of the parent Circus process. This is important
    in particular because importing Django uses a lot of memory, and we want
    circusd to remain lean."""
    with multiprocessing.Pool(1) as pool:
        return pool.apply(_get_pg_host_port)


def wait_for_pgbouncer(*args, max_wait: float = float("inf"), **kwargs) -> bool:
    """This function is used as a `before_start` hook for `circusd`. Its purpose
    is to prevent `Daphne` from starting before `pgbouncer` is ready.

    Args:
        max_wait: The maximum amount of time to wait for `pgbouncer` to start
            listening, in seconds."""
    db_host, db_port = get_pg_host_port()
    start_time = time.monotonic()
    time_of_last_message = 0.0
    while True:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        try:
            sock.connect((db_host, db_port))
            elapsed_time = time.monotonic() - start_time
            print(
                f"pgbouncer is listening on host {db_host} "
                f"and port {db_port} after {elapsed_time:.2f} "
                "seconds;  exiting wait loop."
            )
            return True
        except socket.error as e:
            time_since_last_message = time.monotonic() - time_of_last_message
            # only print every 30s
            if time_since_last_message > 30:
                total_elapsed_time = time.monotonic() - start_time
                print(
                    "pgbouncer not listening after "
                    f"{total_elapsed_time:.2f} seconds ({e}), "
                    "will wait 1s any try again"
                )
                time_of_last_message = time.monotonic()
        finally:
            # clean up the socket no matter what
            sock.close()

        time.sleep(1)

        # Check elapsed time
        elapsed_time = time.monotonic() - start_time
        if elapsed_time > max_wait:
            print(
                f"pgbouncer did not start listening after {elapsed_time:.2f} "
                "seconds; exiting wait loop."
            )
            return False


if __name__ == "__main__":
    wait_for_pgbouncer()
