#!/usr/bin/env python3
from __future__ import annotations

#   Copyright Oli Schacher, Fumail Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


# This tool is used to filter messages in fuglu from the command line

import argparse
from fuglu.scansession import SessionHandler
from fuglu.protocolbase import ProtocolHandler
from fuglu.core import MainController
from fuglu.shared import Suspect, FuConfigParser
from fuglu.connectors.smtpconnector import buildmsgsource
from fuglu.asyncprocpool import get_event_loop
import sys
import os
import tempfile
import logging
import socket
from typing import Any


class DummySocket:
    def __init__(self) -> None:
        pass

    def getsockname(self) -> tuple[str, int]:
        return '', 0


def load_message(filename: str) -> str:
    if filename == '-':
        return sys.stdin.read()
    with open(filename) as stream:
        return stream.read()


class ConsoleConnector(ProtocolHandler):
    protoname = 'CONSOLE'

    def __init__(self, options: argparse.Namespace) -> None:
        self.options = options
        self.socket = DummySocket()  # ty: ignore[invalid-assignment]
        self.exitcode = 1

    def get_suspect(self, **kwargs: Any) -> Suspect:  # noqa: ANN401
        del kwargs
        (handle, tempfilename) = tempfile.mkstemp(
            prefix='fuglu')

        fd = os.fdopen(handle, 'w+b')
        content = load_message(self.options.input)
        try:
            fd.write(content.encode("utf-8", "strict"))
        except Exception as e:
            from inspect import currentframe, getframeinfo
            frame = currentframe()
            if frame is not None:
                frameinfo = getframeinfo(frame)
                sys.stderr.write(f"{frameinfo.filename}:{frameinfo.lineno} {str(e)}")
            raise

        fd.close()
        fromaddr = "unknown@example.org"
        if self.options.sender:
            fromaddr = self.options.sender
        toaddr = "unknown@example.org"
        if self.options.recipient:
            toaddr = self.options.recipient
        suspect = Suspect(fromaddr, toaddr, tempfilename)
        suspect.recipients = [toaddr]
        return suspect

    def commitback(self, suspect: Suspect) -> None:
        msgcontent = buildmsgsource(suspect)
        print(msgcontent)
        self.exitcode = 0
        sys.stderr.write('DUNNO\n')

    def defer(self, reason: str) -> None:
        self.exitcode = 4
        sys.stderr.write("DEFER\n")

    def discard(self, reason: str) -> None:
        self.exitcode = 2
        sys.stderr.write("DISCARD\n")

    def reject(self, reason: str) -> None:
        self.exitcode = 3
        sys.stderr.write("REJECT\n")


class ConsoleInterface:

    def __init__(self, options: argparse.Namespace) -> None:
        self.options = options

    def _load_config(self) -> FuConfigParser:
        theconfigfile = self.options.config
        dconfdir = '/etc/fuglu/conf.d'
        config = FuConfigParser()
        if not os.path.exists(theconfigfile):
            print(
                f"Configfile ({theconfigfile}) not found. Please create it by renaming the .dist file and modifying it to your needs")
            sys.exit(1)
        with open(theconfigfile) as fp:
            config.read_file(fp)
        # load conf.d
        if theconfigfile == '/etc/fuglu/fuglu.conf' and dconfdir and os.path.isdir(dconfdir):
            filelist = os.listdir(dconfdir)
            configfiles = [dconfdir + '/' + c for c in filelist if c.endswith('.conf')]
            config.read(configfiles)
        return config

    def _boot_controller(self, config: FuConfigParser) -> MainController:
        controller = MainController(config)
        controller.propagate_core_defaults()
        controller.load_extensions()
        controller.load_plugins()
        return controller

    def direct(self) -> int:
        config = self._load_config()
        controller = self._boot_controller(config)
        connector = ConsoleConnector(self.options)
        appenders = []
        if self.options.appenders:
            appenders = controller.appenders
        session = SessionHandler(connector, config, controller.prependers,
                                 controller.plugins, appenders, self.options.port, {})
        event_loop = get_event_loop('fuglu_client')
        event_loop.run_until_complete(session.handlesession())
        return connector.exitcode

    def netcat(self) -> int:
        content = load_message(self.options.input)
        ipvers = socket.AF_INET
        host = self.options.host
        if ':' in host:
            ipvers = socket.AF_INET6
        s = socket.socket(ipvers, socket.SOCK_STREAM)
        s.settimeout(20)
        port = self.options.port
        s.connect((host, port))
        sockfile = s.makefile('rw')
        sockfile.readline()  # discard banner
        sockfile.write(content)
        sockfile.flush()
        s.shutdown(socket.SHUT_WR)
        reply = sockfile.read()
        retcode, message = reply.split(':', 1)
        print(message)
        sys.stderr.write(f"{retcode}\n")
        excode = 1
        if retcode == 'DUNNO':
            excode = 0
        elif retcode == 'DEFER':
            excode = 4
        elif retcode == 'DISCARD':
            excode = 2
        elif retcode == 'REJECT':
            excode = 3
        return excode


def doit(options: argparse.Namespace) -> None:
    interface = ConsoleInterface(options)
    retcode = interface.direct() if options.direct else interface.netcat()
    if options.exitcodes:
        sys.exit(retcode)


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--input", default="-", help="message input file. - for stdin")
    parser.add_argument("-s", "--sender", help="envelope sender (only used in direct mode)")
    parser.add_argument("-r", "--recipient", help="envelope recipient (only used in direct mode)")
    parser.add_argument("--host", default="localhost", help="host where fuglu daemon is running")
    parser.add_argument("-p", "--port", type=int, default=20099, help="port on which the netcat connector is listening")
    parser.add_argument("-d", "--direct", action="store_true",
                        help="don't connect to a running fuglu, start your own scanning process")
    parser.add_argument("-a", "--appenders", action="store_true",
                        help="run appenders in direct mode as well. Only used when -d is used")
    parser.add_argument("-e", "--exitcodes", action="store_true",
                        help="exit >1 to indicate a message was DEFERRED or REJECTED")
    parser.add_argument("-c", "--config", default="/etc/fuglu/fuglu.conf",
                        help="use a different config file in direct mode")

    options = parser.parse_args()

    doit(options)
