#!/usr/bin/env python3
import logging
import sys
import time
from importlib.metadata import version
from pathlib import Path
from typing import Tuple

import tmtccmd
from cfdppy.handler import RemoteEntityCfgTable
from cfdppy.mib import (
    IndicationCfg,
    LocalEntityCfg,
    RemoteEntityCfg,
)
from spacepackets.cfdp import (
    ChecksumType,
    TransmissionMode,
)
from spacepackets.ecss import PusVerificator
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
from spacepackets.version import get_version as get_sp_version
from tmtccmd import BackendBase
from tmtccmd.config import (
    SetupWrapper,
    default_json_path,
    params_to_procedure_conversion,
)
from tmtccmd.config.args import (
    PreArgsParsingWrapper,
    ProcedureParamsWrapper,
    SetupParams,
    perform_tree_printout,
)
from tmtccmd.core import BackendRequest
from tmtccmd.fsfw.tmtc_printer import FsfwTmTcPrinter
from tmtccmd.logging import add_colorlog_console_logger
from tmtccmd.logging.pus import (
    RawTmtcTimedLogWrapper,
    RegularTmtcLogWrapper,
    TimedLogWhen,
)
from tmtccmd.pus import VerificationWrapper
from tmtccmd.tmtc import CcsdsTmHandler

from eive_tmtc import APP_LOGGER
from eive_tmtc.cfdp.fault_handler import EiveCfdpFaultHandler
from eive_tmtc.cfdp.handler import CfdpInCcsdsHandler
from eive_tmtc.cfdp.tm import CfdpInCcsdsWrapper
from eive_tmtc.cfdp.user import EiveCfdpUser, EiveCheckTimerProvider
from eive_tmtc.config.definitions import (
    CFDP_APID,
    CFDP_LOCAL_ENTITY_ID,
    CFDP_REMOTE_ENTITY_ID,
    PUS_APID,
)
from eive_tmtc.config.hook import EiveHookObject
from eive_tmtc.pus_tc.tc_handler import TcHandler
from eive_tmtc.pus_tm.pus_handler import PusHandler, UnknownApidHandler

_LOGGER = APP_LOGGER
_LOG_LEVEL = logging.INFO

# Put rotating file logger parameters here for quick changes
ROTATING_TIMED_LOGGER_INTERVAL_WHEN = TimedLogWhen.PER_MINUTE
ROTATING_TIMED_LOGGER_INTERVAL = 30


class CustomCcsdsTmHandler(CcsdsTmHandler):
    def user_hook(self, apid: int, packet: bytes):
        _LOGGER.debug(f"Received packet {packet.hex(sep=',')} with APID {apid}")


def setup_params() -> Tuple[SetupWrapper, int]:
    hook_obj = EiveHookObject(default_json_path())
    params = SetupParams()
    parser_wrapper = PreArgsParsingWrapper()
    parser_wrapper.create_default_parent_parser()
    parser_wrapper.create_default_parser()
    tmtc_parser, cfdp_parser = parser_wrapper.add_def_proc_and_cfdp_as_subparsers()
    tmtc_parser.add_argument(
        "--hk",
        help="HK output level",
        default=2,
    )
    post_arg_parsing_wrapper = parser_wrapper.parse(
        setup_params=params, hook_obj=hook_obj
    )
    tmtccmd.init_printout(post_arg_parsing_wrapper.use_gui)
    use_prompts = not post_arg_parsing_wrapper.use_gui
    proc_param_wrapper = ProcedureParamsWrapper()
    if use_prompts:
        post_arg_parsing_wrapper.set_params_with_prompts(proc_param_wrapper)
    else:
        post_arg_parsing_wrapper.set_params_without_prompts(proc_param_wrapper)
    if hasattr(post_arg_parsing_wrapper.args_raw, "hk"):
        hk_level = int(post_arg_parsing_wrapper.args_raw.hk)
    else:
        hk_level = 0
    if params.cmd_params.print_tree:
        perform_tree_printout(params.cmd_params, hook_obj.get_command_definitions())
        sys.exit(0)
    params.apid = PUS_APID
    if params.com_if is None:
        raise ValueError("could not determine a COM interface.")
    setup_wrapper = SetupWrapper(
        hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_param_wrapper
    )
    return setup_wrapper, hk_level


def setup_cfdp_handler() -> CfdpInCcsdsWrapper:
    fh_base = EiveCfdpFaultHandler()
    cfdp_cfg = LocalEntityCfg(
        local_entity_id=CFDP_LOCAL_ENTITY_ID,
        indication_cfg=IndicationCfg(),
        default_fault_handlers=fh_base,
    )
    self_as_remote = RemoteEntityCfg(
        closure_requested=False,
        entity_id=CFDP_LOCAL_ENTITY_ID,
        max_file_segment_len=990,
        max_packet_len=1024,
        check_limit=5,
        crc_on_transmission=False,
        crc_type=ChecksumType.CRC_32,
        default_transmission_mode=TransmissionMode.UNACKNOWLEDGED,
    )
    remote_cfg = RemoteEntityCfg(
        closure_requested=False,
        entity_id=CFDP_REMOTE_ENTITY_ID,
        max_file_segment_len=990,
        max_packet_len=1024,
        check_limit=5,
        crc_on_transmission=False,
        crc_type=ChecksumType.CRC_32,
        default_transmission_mode=TransmissionMode.UNACKNOWLEDGED,
    )
    check_timer_provider = EiveCheckTimerProvider()
    cfdp_seq_count_provider = FileSeqCountProvider(
        max_bit_width=16, file_name=Path("seqcnt_cfdp_transaction.txt")
    )
    cfdp_ccsds_seq_count_provider = PusFileSeqCountProvider(
        file_name=Path("seqcnt_cfdp_in_ccsds.txt")
    )
    cfdp_user = EiveCfdpUser()
    cfdp_in_ccsds_handler = CfdpInCcsdsHandler(
        cfg=cfdp_cfg,
        check_timer_provider=check_timer_provider,
        remote_cfg_table=RemoteEntityCfgTable([remote_cfg, self_as_remote]),
        ccsds_apid=CFDP_APID,
        ccsds_seq_cnt_provider=cfdp_ccsds_seq_count_provider,
        cfdp_seq_cnt_provider=cfdp_seq_count_provider,
        user=cfdp_user,
    )
    return CfdpInCcsdsWrapper(cfdp_in_ccsds_handler)


def setup_tmtc_handlers(
    verificator: PusVerificator,
    printer: FsfwTmTcPrinter,
    raw_logger: RawTmtcTimedLogWrapper,
    gui: bool,
    hk_level: int,
) -> Tuple[CcsdsTmHandler, TcHandler]:
    cfdp_in_ccsds_wrapper = setup_cfdp_handler()
    verification_wrapper = VerificationWrapper(
        verificator, _LOGGER, printer.file_logger
    )
    pus_handler = PusHandler(verification_wrapper, printer, raw_logger, hk_level)
    ccsds_handler = CustomCcsdsTmHandler(generic_handler=UnknownApidHandler(None))
    ccsds_handler.add_apid_handler(pus_handler)
    ccsds_handler.add_apid_handler(cfdp_in_ccsds_wrapper)
    seq_count_provider = PusFileSeqCountProvider()
    assert printer.file_logger is not None
    tc_handler = TcHandler(
        seq_count_provider=seq_count_provider,
        pus_verificator=verificator,
        high_level_file_logger=printer.file_logger,
        raw_pus_logger=raw_logger,
        gui=gui,
        cfdp_in_ccsds_handler=cfdp_in_ccsds_wrapper.handler,
    )
    return ccsds_handler, tc_handler


def setup_backend(
    setup_wrapper: SetupWrapper,
    tc_handler: TcHandler,
    ccsds_handler: CcsdsTmHandler,
) -> BackendBase:
    init_proc = params_to_procedure_conversion(setup_wrapper.proc_param_wrapper)
    tmtc_backend = tmtccmd.create_default_tmtc_backend(
        setup_wrapper=setup_wrapper,
        tm_handler=ccsds_handler,
        tc_handler=tc_handler,
        init_procedure=init_proc,
    )
    tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=setup_wrapper.hook_obj)
    return tmtc_backend


def main():  # noqa C901: Complexity okay here.
    print(f"-- eive tmtc v{version('eive-tmtc')} --")
    print(f"-- spacepackets v{get_sp_version()} --")
    add_colorlog_console_logger(_LOGGER)
    # TODO: -V CLI argument to enable this?
    _LOGGER.setLevel(_LOG_LEVEL)
    try:
        setup_wrapper, hk_level = setup_params()
    except KeyboardInterrupt as e:
        _LOGGER.info(f"{e}. Exiting")
        sys.exit(0)

    tmtc_logger = RegularTmtcLogWrapper()
    printer = FsfwTmTcPrinter(tmtc_logger.logger)
    raw_logger = RawTmtcTimedLogWrapper(
        when=ROTATING_TIMED_LOGGER_INTERVAL_WHEN,
        interval=ROTATING_TIMED_LOGGER_INTERVAL,
    )
    pus_verificator = PusVerificator()
    ccsds_handler, tc_handler = setup_tmtc_handlers(
        pus_verificator, printer, raw_logger, setup_wrapper.params.use_gui, hk_level
    )

    tmtccmd.setup(setup_wrapper)
    tmtc_backend = setup_backend(
        setup_wrapper=setup_wrapper, ccsds_handler=ccsds_handler, tc_handler=tc_handler
    )
    try:
        while True:
            state = tmtc_backend.periodic_op(None)
            tc_handler.cfdp_in_ccsds_handler.state_machine()
            if state.request == BackendRequest.TERMINATION_NO_ERROR:
                sys.exit(0)
            elif state.request == BackendRequest.DELAY_IDLE:
                _LOGGER.info("TMTC Client in IDLE mode")
                time.sleep(3.0)
            elif state.request == BackendRequest.DELAY_LISTENER:
                if tc_handler.proxy_op:
                    time.sleep(0.1)
                    continue
                if tc_handler.cfdp_done():
                    _LOGGER.info("CFDP transaction done, closing client")
                    sys.exit(0)
                time.sleep(0.5)
            elif state.request == BackendRequest.DELAY_CUSTOM:
                if state.next_delay.total_seconds() < 0.5:
                    time.sleep(state.next_delay.total_seconds())
                else:
                    time.sleep(0.5)
            elif state.request == BackendRequest.CALL_NEXT:
                pass
    except KeyboardInterrupt:
        sys.exit(0)


if __name__ == "__main__":
    main()