from typing import Any, Optional

from primaite import getLogger
from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode
from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port
from primaite.simulator.system.services.ftp.ftp_service import FTPServiceABC

_LOGGER = getLogger(__name__)


class FTPServer(FTPServiceABC):
    """
    A class for simulating an FTP server service.

    This class inherits from the `Service` class and provides methods to emulate FTP
    RFC 959: https://datatracker.ietf.org/doc/html/rfc959
    """

    server_password: Optional[str] = None
    """Password needed to connect to FTP server. Default is None."""

    def __init__(self, **kwargs):
        kwargs["name"] = "FTPServer"
        kwargs["port"] = Port.FTP
        kwargs["protocol"] = IPProtocol.TCP
        super().__init__(**kwargs)
        self.start()

    def _process_ftp_command(self, payload: FTPPacket, session_id: Optional[str] = None, **kwargs) -> FTPPacket:
        """
        Process the command in the FTP Packet.

        :param: payload: The FTP Packet to process
        :type: payload: FTPPacket
        :param: session_id: session ID linked to the FTP Packet. Optional.
        :type: session_id: Optional[str]
        """
        # error code by default
        payload.status_code = FTPStatusCode.ERROR

        # if server service is down, return error
        if not self._can_perform_action():
            return payload

        self.sys_log.info(f"{self.name}: Received FTP {payload.ftp_command.name} {payload.ftp_command_args}")

        if payload.ftp_command is not None:
            self.sys_log.info(f"Received FTP {payload.ftp_command.name} command.")

        # process server specific commands, otherwise call super
        if payload.ftp_command == FTPCommand.PORT:
            # check that the port is valid
            if isinstance(payload.ftp_command_args, Port) and payload.ftp_command_args.value in range(0, 65535):
                # return successful connection
                self.add_connection(connection_id=session_id, session_id=session_id)
                payload.status_code = FTPStatusCode.OK
                return payload

            self.sys_log.error(f"Invalid Port {payload.ftp_command_args}")
            return payload

        if payload.ftp_command == FTPCommand.QUIT:
            self.terminate_connection(connection_id=session_id)
            payload.status_code = FTPStatusCode.OK
            return payload

        return super()._process_ftp_command(payload=payload, session_id=session_id, **kwargs)

    def receive(self, payload: Any, session_id: Optional[str] = None, **kwargs) -> bool:
        """Receives a payload from the SessionManager."""
        if not isinstance(payload, FTPPacket):
            self.sys_log.warning(f"{self.name}: Payload is not an FTP packet")
            self.sys_log.debug(f"{self.name}: {payload}")
            return False

        if not super().receive(payload=payload, session_id=session_id, **kwargs):
            return False

        """
        Ignore ftp payload if status code is defined.

        This means that an FTP server has already handled the packet and
        prevents an FTP request loop - FTP client and servers can exist on
        the same node.
        """
        if payload.status_code is not None:
            return False

        self._process_ftp_command(payload=payload, session_id=session_id)
        return True
