# coding: utf-8
import asyncio
import ipaddress
import os
import re

from typing import Dict, List, Union, Any  # noqa: F401

from fastapi import (  # noqa: F401
    APIRouter,
    Body,
    Cookie,
    Depends,
    Form,
    Header,
    Path,
    Query,
    Response,
    Security,
    status,
    HTTPException,
)
from fastapi.responses import StreamingResponse

from server.data.definitions import Groups, API_SUB_PATH, ErrorTypes, Messages, SystemdServices
from server.models.extra_models import TokenModel  # noqa: F401
from server.models.action import Action
from server.models.error import Error
from server.security_api import get_token_ApiKeyAuth, checkTokenCapabilities
from urllib.parse import urlparse

router = APIRouter(prefix=API_SUB_PATH)


@router.get(
    "/actions/ping",
    responses={
        200: {"model": Action, "description": "Gets the action result."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "Action not found."},
    },
    tags=["action"],
    response_model_by_alias=True,
)
async def actions_ping_get(
    address: str, token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)
) -> StreamingResponse:
    """Trigger a specific action."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TRIGGER_USER_ACTIONS.value)

    # ---------------------------------------- Check files where given correct ----------------------------------------

    if not ipaddress:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.WRONG_IP_FORMAT.value,
            message=Messages.WRONG_IP_FORMAT.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------------------ Start the action ------------------------------------------------
    # Variables
    isIp = False

    # Check if it's an ip address
    try:
        isIp = ipaddress.ip_address(address)
    except ValueError:
        pass

    # Check if it's a domain name
    domain_name_pattern = re.compile(r"^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$")
    isUrl = re.match(domain_name_pattern, address)

    if not isIp and not isUrl:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.WRONG_IP_URL_FORMAT.value,
            message=Messages.WRONG_IP_URL_FORMAT.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    process = await asyncio.create_subprocess_shell(
        f"ping -c 10 {address}", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )

    async def generator():
        async for line in process.stdout:
            yield line
        async for line in process.stderr:
            yield line

    try:
        return StreamingResponse(generator(), media_type="text/plain")
    except asyncio.CancelledError:
        # If the request is cancelled, cancel the subprocess
        process.kill()


@router.get(
    "/actions/traceroute",
    responses={
        200: {"model": Action, "description": "Gets the action result."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "Action not found."},
    },
    tags=["action"],
    response_model_by_alias=True,
)
async def actions_traceroute_get(
    address: str, token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)
) -> StreamingResponse:
    """Trigger a specific action."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TRIGGER_USER_ACTIONS.value)

    # ---------------------------------------- Check files where given correct ----------------------------------------

    if not ipaddress:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.WRONG_IP_FORMAT.value,
            message=Messages.WRONG_IP_FORMAT.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------------------ Start the action ------------------------------------------------
    # Variables
    isIp = False

    # Check if it's an ip address
    try:
        isIp = ipaddress.ip_address(address)
    except ValueError:
        pass

    # Check if it's a URL
    url_pattern = re.compile(r"^(https?:\/\/)?(www\.)?[a-zA-Z0-9-]+(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?$")
    isUrl = re.match(url_pattern, address)

    if not isIp and not isUrl:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.WRONG_IP_URL_FORMAT.value,
            message=Messages.WRONG_IP_URL_FORMAT.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    process = await asyncio.create_subprocess_shell(
        f"traceroute {address}", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )

    async def generator():
        async for line in process.stdout:
            yield line
        async for line in process.stderr:
            yield line

    try:
        return StreamingResponse(generator(), media_type="text/plain")
    except asyncio.CancelledError:
        # If the request is cancelled, cancel the subprocess
        process.kill()


@router.put(
    "/actions/restart-service",
    responses={
        200: {"model": Action, "description": "Gets the action result."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "Action not found."},
    },
    tags=["action"],
    response_model_by_alias=True,
)
async def actions_restart_service_put(
    service: str, token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)
) -> Union[List, Any]:
    """Trigger a specific action."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TRIGGER_USER_ACTIONS.value)

    # ---------------------------------------- Check service where given correct ----------------------------------------

    # Convert enum members into a list of their values
    systemd_services_list = [service.value for service in SystemdServices]

    if f"{service}.service" not in systemd_services_list:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.ACTION_NOT_FOUND.value,
            message=Messages.ACTION_NOT_FOUND.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------------------ Start the action ------------------------------------------------

    # Do not execute when running in a docker
    if os.path.exists("/.dockerenv"):
        return [{"service-restart": service}]

    process = await asyncio.create_subprocess_shell(
        f"sudo systemctl restart {service}", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    return [{"service-restart": service}]


@router.get(
    "/actions/reboot-device",
    responses={
        200: {"model": Action, "description": "Gets the action result."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "Action not found."},
    },
    tags=["action"],
    response_model_by_alias=True,
)
async def actions_reboot_device_get(token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)) -> Union[List, Any]:
    """Trigger a specific action."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.TRIGGER_USER_ACTIONS.value)

    # ------------------------------------------------ Start the action ------------------------------------------------

    # Do not execute when running in a docker
    if os.path.exists("/.dockerenv"):
        return [{"reboot-device": "triggered"}]

    process = await asyncio.create_subprocess_shell(
        f"sudo systemctl reboot", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    return [{"reboot-device": "triggered"}]


@router.get(
    "/actions/rauc",
    responses={
        200: {"model": Action, "description": "Gets the action result."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "Action not found."},
    },
    tags=["action"],
    response_model_by_alias=True,
)
async def actions_rauc_get(
    address: str, token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)
) -> StreamingResponse:
    """Trigger a rauc install."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.UPLOAD_FIRMWARE.value)

    # ---------------------------------------- Check address where given correct --------------------------------------

    if not address:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.WRONG_URL_FORMAT.value,
            message=Messages.WRONG_URL_FORMAT.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    try:
        result = urlparse(address)
    except ValueError:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.WRONG_URL_FORMAT.value,
            message=Messages.WRONG_URL_FORMAT.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # ------------------------------------------------ Start the action ------------------------------------------------

    process = await asyncio.create_subprocess_shell(
        f"rauc install {address}", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )

    async def generator():
        async for line in process.stdout:
            yield line
        async for line in process.stderr:
            yield line

    try:
        return StreamingResponse(generator(), media_type="text/plain")
    except asyncio.CancelledError:
        # If the request is cancelled, cancel the subprocess
        process.kill()
