# SPDX-FileCopyrightText: 2021 Dalibo
#
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

from collections.abc import Iterator
from typing import TYPE_CHECKING, Any, Literal, Optional

import pluggy
from pgtoolkit.conf import Configuration
from pydantic.fields import FieldInfo
from typing_extensions import TypeAlias

from . import __name__ as pkgname

if TYPE_CHECKING:
    from .models import interface, system
    from .models.system import BaseInstance, Instance, PostgreSQLInstance
    from .postgresql import Standby
    from .settings import Settings
    from .types import ConfigChanges, ServiceManifest, Status

hookspec = pluggy.HookspecMarker(pkgname)

FirstResult: TypeAlias = Literal[True]
#: At least one hook implementation is expected to return (True).
MaybeFirstResult: TypeAlias = Optional[Literal[True]]
#: At most one hook implementation is expected to return (True), but the hook
#  is allowed to not return (e.g. if no returning hook implementation is
#  registered).


@hookspec
async def site_configure_install(
    settings: Settings, header: str, env: dict[str, Any]
) -> None:
    """Global site installation hook.

    This is typically used to install site-wise configuration files or create
    data directories.

    Respective implementation should be idempotent (possibly no-op, in case of
    re-configuration), and should avoid overwriting existing files.
    """
    raise NotImplementedError


@hookspec
async def site_configure_start(settings: Settings) -> None:
    """Start site-wise services upon site configuration."""
    raise NotImplementedError


@hookspec
async def site_configure_stop(settings: Settings) -> None:
    """Stop site-wise services upon site de-configuration."""
    raise NotImplementedError


@hookspec
async def site_configure_uninstall(settings: Settings) -> None:
    """Global site uninstallation hook.

    Respective implementation should be idempotent (possibly no-op, in case of
    already de-configured services).
    """
    raise NotImplementedError


@hookspec
def site_configure_check(settings: Settings, log: bool) -> Iterator[bool]:
    """Check installation in each plugin.

    Yield True values if installation is okay and False otherwise, most likely
    with an ERROR message logged (if 'log' is True).
    """
    raise NotImplementedError


@hookspec
def systemd_units() -> list[str]:
    """Systemd unit names used by each plugin."""
    raise NotImplementedError


@hookspec
def systemd_unit_templates(
    settings: Settings, env: dict[str, Any]
) -> Iterator[tuple[str, str]]:
    """Systemd unit templates used by each plugin."""
    raise NotImplementedError


@hookspec
def system_lookup(instance: PostgreSQLInstance) -> Any | None:
    """Look up for the satellite service object on system that matches specified instance."""
    raise NotImplementedError


@hookspec
async def get(instance: Instance) -> ServiceManifest | None:
    """Return the description the satellite service bound to specified instance."""
    raise NotImplementedError


@hookspec
def interface_model() -> tuple[str, Any, Any]:
    """The interface model for satellite component provided plugin."""
    raise NotImplementedError


@hookspec
def instance_settings(
    manifest: interface.Instance, instance: BaseInstance
) -> Configuration:
    """Called before the PostgreSQL instance settings is written."""
    raise NotImplementedError


@hookspec(firstresult=True)
async def standby_model(
    instance: system.Instance, standby: system.Standby, running: bool
) -> Standby:
    """The interface model holding standby information, if 'instance' is a
    plain standby.

    Only one implementation should be invoked so call order and returned value
    matter.

    An implementation may raise a ValueError to interrupt hook execution.
    """
    raise NotImplementedError


@hookspec
async def instance_configured(
    instance: system.PostgreSQLInstance,
    manifest: interface.Instance,
    config: Configuration,
    changes: ConfigChanges,
    creating: bool,
    upgrading_from: Instance | None,
) -> None:
    """Called when the PostgreSQL instance got (re-)configured."""
    raise NotImplementedError


@hookspec
async def instance_dropped(instance: Instance) -> None:
    """Called when the PostgreSQL instance got dropped."""
    raise NotImplementedError


@hookspec
async def instance_started(instance: Instance) -> None:
    """Called when the PostgreSQL instance got started."""
    raise NotImplementedError


@hookspec
async def instance_stopped(instance: Instance) -> None:
    """Called when the PostgreSQL instance got stopped."""
    raise NotImplementedError


@hookspec
async def instance_promoted(instance: Instance) -> None:
    """Called when the PostgreSQL instance got promoted."""
    raise NotImplementedError


@hookspec
def instance_env(instance: Instance) -> dict[str, str]:
    """Return environment variables for instance defined by the plugin."""
    raise NotImplementedError


@hookspec
def instance_upgraded(old: PostgreSQLInstance, new: PostgreSQLInstance) -> None:
    """Called when 'old' PostgreSQL instance got upgraded as 'new'."""
    raise NotImplementedError


@hookspec
async def instance_status(instance: Instance) -> tuple[Status, str] | None:
    """Return instance status"""
    raise NotImplementedError


@hookspec
def role_model() -> tuple[str, Any, FieldInfo]:
    """Return the definition for an extra field to the Role interface model
    provided by a plugin.
    """
    raise NotImplementedError


@hookspec
def role_change(role: interface.BaseRole, instance: PostgreSQLInstance) -> bool:
    """Called when 'role' changed in 'instance' (be it a create, an update or a deletion).

    Return True if any change happened during hook invocation.
    """
    raise NotImplementedError


@hookspec
def role_inspect(instance: PostgreSQLInstance, name: str) -> dict[str, Any]:
    """Return extra attributes for 'name' role from plugins."""
    raise NotImplementedError


@hookspec
def rolename(settings: Settings) -> str:
    """Return the name of role used by a plugin."""
    raise NotImplementedError


@hookspec
def role(settings: Settings, manifest: interface.Instance) -> interface.Role:
    """Return the role used by a plugin, to be created at instance creation."""
    raise NotImplementedError


@hookspec
def database(settings: Settings, manifest: interface.Instance) -> interface.Database:
    """Return the database used by a plugin, to be created at instance creation."""
    raise NotImplementedError


@hookspec(firstresult=True)
async def initdb(manifest: interface.Instance, instance: BaseInstance) -> FirstResult:
    """Initialize a PostgreSQL database cluster.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec
async def patroni_create_replica_method(
    manifest: interface.Instance,
    instance: BaseInstance,
) -> tuple[str, dict[str, Any]] | None:
    raise NotImplementedError


@hookspec(firstresult=True)
def postgresql_editable_conf(instance: BaseInstance) -> str:
    """Return the content of editable postgresql.conf.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def configure_postgresql(
    manifest: interface.Instance, configuration: Configuration, instance: BaseInstance
) -> ConfigChanges:
    """Configure PostgreSQL and return 'changes' to postgresql.conf.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
def configure_auth(instance: BaseInstance, manifest: interface.Instance) -> bool:
    """Configure authentication for PostgreSQL (pg_hba.conf, pg_ident.conf).

    Only one implementation should be invoked so call order and returned value
    matter.

    If returning True, PostgreSQL should be restarted by the caller.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def start_postgresql(
    instance: PostgreSQLInstance, foreground: bool, wait: bool
) -> FirstResult:
    """Start PostgreSQL for specified 'instance'.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def stop_postgresql(
    instance: PostgreSQLInstance, mode: str, wait: bool, deleting: bool
) -> FirstResult:
    """Stop PostgreSQL for specified 'instance'.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def restart_postgresql(
    instance: PostgreSQLInstance, mode: str, wait: bool
) -> FirstResult:
    """Restart PostgreSQL for specified 'instance'.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def reload_postgresql(instance: PostgreSQLInstance) -> FirstResult:
    """Reload PostgreSQL configuration for 'instance'.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def promote_postgresql(instance: Instance) -> FirstResult:
    """Promote PostgreSQL for 'instance'.

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
def postgresql_service_name(instance: BaseInstance) -> str:
    """Return the system service name (e.g.  postgresql).

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def enable_service(
    settings: Settings, service: str, name: str | None
) -> FirstResult | None:
    """Enable a service

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def disable_service(
    settings: Settings, service: str, name: str | None, now: bool | None
) -> FirstResult | None:
    """Disable a service

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def start_service(
    settings: Settings, service: str, name: str | None
) -> MaybeFirstResult:
    """Start a service for a plugin

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def stop_service(
    settings: Settings, service: str, name: str | None
) -> MaybeFirstResult:
    """Stop a service for a plugin

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def restart_service(
    settings: Settings, service: str, name: str | None
) -> MaybeFirstResult:
    """Restart a service for a plugin

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def service_status(settings: Settings, service: str, name: str | None) -> Status:
    """Return a service status for a plugin

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def schedule_service(
    settings: Settings, service: str, name: str
) -> MaybeFirstResult:
    """Schedule a job through timer

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def unschedule_service(
    settings: Settings, service: str, name: str, now: bool | None
) -> MaybeFirstResult:
    """Unchedule a job

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def start_timer(settings: Settings, service: str, name: str) -> MaybeFirstResult:
    """Start a timer

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec(firstresult=True)
async def stop_timer(settings: Settings, service: str, name: str) -> FirstResult:
    """Stop a timer

    Only one implementation should be invoked so call order and returned value
    matter.
    """
    raise NotImplementedError


@hookspec
def logrotate_config(settings: Settings) -> str:
    """Return logrotate configuration for the service matching specified instance."""
    raise NotImplementedError


@hookspec
def rsyslog_config(settings: Settings) -> str:
    """Return rsyslog configuration for the service matching specified instance."""
    raise NotImplementedError
