"""Setup the service."""

from __future__ import annotations

import asyncio
import contextlib
from dataclasses import fields
from pathlib import Path
from typing import TYPE_CHECKING

import docker
import docker.errors
from docker.models.containers import Container
from docker.models.images import Image
from ubo_gui.menu.types import ActionItem, HeadedMenu, HeadlessMenu, Item, SubMenuItem

from ubo_app.constants import DOCKER_INSTALLATION_LOCK_FILE, SERVER_SOCKET_PATH
from ubo_app.store import autorun, dispatch
from ubo_app.store.main import RegisterRegularAppAction
from ubo_app.store.services.docker import (
    DockerSetStatusAction,
    DockerState,
    DockerStatus,
)
from ubo_app.utils.async_ import create_task
from ubo_app.utils.monitor_unit import monitor_unit
from ubo_app.utils.server import send_command

if TYPE_CHECKING:
    from collections.abc import Callable


def install_docker() -> None:
    """Install Docker."""
    if Path(SERVER_SOCKET_PATH).exists():

        async def act() -> None:
            await send_command('docker install')
            dispatch(DockerSetStatusAction(status=DockerStatus.INSTALLING))

        create_task(act())


def run_docker() -> None:
    """Install Docker."""

    async def act() -> None:
        await send_command('docker start')
        dispatch(DockerSetStatusAction(status=DockerStatus.UNKNOWN))

    create_task(act())


def stop_docker() -> None:
    """Install Docker."""

    async def act() -> None:
        await send_command('docker stop')
        dispatch(DockerSetStatusAction(status=DockerStatus.UNKNOWN))

    create_task(act())


async def check_docker() -> None:
    """Check if Docker is installed."""
    from image import update_container
    from reducer import IMAGES

    process = await asyncio.create_subprocess_exec(
        '/usr/bin/env',
        'which',
        'docker',
    )
    await process.wait()
    is_installed = process.returncode == 0

    is_running = False
    with contextlib.suppress(Exception):
        docker_client = docker.from_env()
        is_running = docker_client.ping()

        for container in docker_client.containers.list(all=True):
            if not isinstance(container, Container):
                continue

            with contextlib.suppress(docker.errors.DockerException):
                container_image = container.image
                for image_id, image_description in IMAGES.items():
                    if (
                        isinstance(container_image, Image)
                        and image_description.path in container_image.tags
                    ):
                        update_container(image_id, container)

        docker_client.close()

    if is_running:
        dispatch(DockerSetStatusAction(status=DockerStatus.RUNNING))
    elif is_installed:
        dispatch(DockerSetStatusAction(status=DockerStatus.NOT_RUNNING))
    elif Path(DOCKER_INSTALLATION_LOCK_FILE).exists():
        dispatch(DockerSetStatusAction(status=DockerStatus.INSTALLING))
    else:
        dispatch(DockerSetStatusAction(status=DockerStatus.NOT_INSTALLED))


@autorun(lambda state: state.docker.service.status)
def setup_menu(status: DockerStatus) -> HeadedMenu:
    """Get the menu items for the Docker service."""
    title = 'Setup Docker'
    if status == DockerStatus.UNKNOWN:
        return HeadedMenu(
            title=title,
            heading='Checking',
            sub_heading='Checking Docker service status',
            items=[],
            placeholder='',
        )
    if status == DockerStatus.NOT_INSTALLED:
        return HeadedMenu(
            title=title,
            heading='Docker is not Installed',
            sub_heading='Install it to enjoy the power of Docker on your Ubo pod',
            items=[
                ActionItem(
                    label='Install Docker',
                    icon='󰶮',
                    action=install_docker,
                ),
            ],
        )
    if status == DockerStatus.INSTALLING:
        return HeadedMenu(
            title=title,
            heading='Installing...',
            sub_heading='Docker is being installed',
            items=[],
            placeholder='',
        )
    if status == DockerStatus.NOT_RUNNING:
        return HeadedMenu(
            title=title,
            heading='Docker is not Running',
            sub_heading='Run it to enjoy the power of Docker on your Ubo pod',
            items=[
                ActionItem(
                    label='Start Docker',
                    icon='󰐊',
                    action=run_docker,
                ),
            ],
        )
    if status == DockerStatus.RUNNING:
        return HeadedMenu(
            title=title,
            heading='Docker is Running',
            sub_heading='Enjoy the power of Docker on your Ubo pod',
            items=[
                ActionItem(
                    label='Stop Docker',
                    icon='󰓛',
                    action=stop_docker,
                ),
            ],
        )
    if status == DockerStatus.ERROR:
        return HeadedMenu(
            title=title,
            heading='Docker Error',
            sub_heading='Please check the logs for more information',
            items=[],
            placeholder='',
        )

    msg = f'Unknown status: {status}'
    raise ValueError(msg)


def setup_menu_action() -> Callable[[], HeadedMenu]:
    """Get the menu items for the Docker service."""
    create_task(check_docker())
    return setup_menu


@autorun(lambda state: state.docker)
def docker_menu_items(state: DockerState) -> list[Item]:
    """Get the menu items for the Docker service."""
    create_task(check_docker())
    items: list[Item] = [
        ActionItem(
            label='Setup Docker',
            icon='',
            action=setup_menu_action,
        ),
    ]

    if state.service.status == DockerStatus.RUNNING:
        from image import IMAGE_MENUS

        items.append(
            SubMenuItem(
                label='Docker Containers',
                icon='󱣘',
                sub_menu=HeadlessMenu(
                    title='Docker Containers',
                    items=[
                        ActionItem(
                            label=getattr(state, image_id).label,
                            icon=getattr(state, image_id).icon,
                            action=IMAGE_MENUS[image_id],
                        )
                        for image_id in (field.name for field in fields(state))
                        if image_id not in (field.name for field in fields(DockerState))
                    ],
                    placeholder='No Docker containers',
                ),
            ),
        )

    return items


def docker_menu_item_action() -> HeadlessMenu:
    """Get the menu items for the Docker service."""
    return HeadlessMenu(
        title='Docker',
        items=docker_menu_items,
    )


docker_main_menu = ActionItem(
    label='Docker',
    icon='󰡨',
    action=docker_menu_item_action,
)


def init_service() -> None:
    """Initialize the service."""
    dispatch(RegisterRegularAppAction(menu_item=docker_main_menu))
    create_task(
        monitor_unit(
            'docker.socket',
            lambda status: dispatch(
                DockerSetStatusAction(
                    status=DockerStatus.RUNNING
                    if status in ('active', 'activating', 'reloading')
                    else DockerStatus.NOT_RUNNING,
                ),
            ),
        ),
    )
