#!/usr/bin/python3

import calendar
from functional import seq
import argparse
import getpass
import json
import sys
import os
from datetime import datetime
from datetime import timedelta
from salad.api import Connection
from configparser import ConfigParser
import base64
from pathlib import Path


def date_or_today(date: str) -> datetime:
    if date is None:
        return datetime.today()
    else:
        return datetime.strptime(date, '%Y-%m-%d')


def safe_get(arr, index, default=None):
    try:
        return arr[index]
    except IndexError:
        return default


def print_message(args, message: str):
    if args.verbose:
        print(message)


def print_json(j):
    print(json.dumps(j, indent=4, sort_keys=False))


def sum_duration(x, y) -> float:
    return x + y["duration"]


def open_connection(args) -> Connection:
    return Connection(
        args.user if args.user is not None else input("Bitte den Benutzernamen eingeben (Mitarbeiterkuerzel): "),
        args.password if args.password is not None else getpass.getpass("Bitte das Passwort eingeben: "),
        args.url if args.url is not None else input("Bitte die Salat URL eingeben: "),
    )


def load_config(args, file_name: str) -> ConfigParser:
    config = ConfigParser()

    if os.path.isfile(file_name):
        with open(config_file_name, "r") as config_file:
            config.read_file(config_file)

        if args.user is None and config.has_option(config.default_section, "user"):
            args.user = config.get(config.default_section, "user")

        if args.password is None and config.has_option(config.default_section, "password"):
            base64_bytes = config.get(config.default_section, "password").encode('ascii')
            args.password = base64.b64decode(base64_bytes).decode('ascii')

        if args.url is None and config.has_option(config.default_section, "url"):
            args.url = config.get(config.default_section, "url")

        if args.default_order is None and config.has_option(config.default_section, "default_order"):
            args.default_order = config.get(config.default_section, "default_order")

    return config


def command_show_report(args, conn: Connection, begin: datetime, end: datetime):
    begin_str = begin.strftime("%Y-%m-%d")
    end_str = end.strftime("%Y-%m-%d")
    print_message(args, f"get report from {begin_str} to {end_str}")

    contract = conn.get_contract()
    report = conn.get_report(contract, begin_str, end_str)

    if args.summary:
        summary = report \
            .group_by(lambda x: x["date"]) \
            .map(lambda x: {"date": x[0], "values": seq(x[1]).reduce(sum_duration, 0.0)})
        print_json(summary.to_list())
    else:
        print_json(report.to_list())


def command_show(args):
    with open_connection(args) as connection:
        if args.parameters[0] == "contract":
            print_json(connection.get_contract())

        elif args.parameters[0] == "orders":
            contract_orders = connection.get_contract_orders().to_list()
            print_json(contract_orders)

        elif args.parameters[0] == "employee":
            print_json(connection.get_employee())

        elif args.parameters[0] == "day":
            day = date_or_today(safe_get(args.parameters, 1))
            command_show_report(args, connection, day, day)

        elif args.parameters[0] == "yesterday":
            day = date_or_today(safe_get(args.parameters, 1))
            yesterday = day - timedelta(days=1)
            command_show_report(args, connection, yesterday, yesterday)

        elif args.parameters[0] == "week":
            day = date_or_today(safe_get(args.parameters, 1))
            begin_of_week = day - timedelta(days=day.weekday())
            end_of_week = day + timedelta(days=6 - day.weekday())
            command_show_report(args, connection, begin_of_week, end_of_week)

        elif args.parameters[0] == "month":
            day = date_or_today(safe_get(args.parameters, 1))
            begin_of_month = day.replace(day=1)
            end_of_month = day.replace(day=calendar.monthrange(day.year, day.month)[1])
            command_show_report(args, connection, begin_of_month, end_of_month)

        else:
            raise RuntimeError(f"unknown type {args.parameters[0]}")


def command_report(args):
    with open_connection(args) as connection:
        comment = args.parameters[0]
        duration = float(args.parameters[1])
        order = safe_get(args.parameters, 2)
        day = date_or_today(safe_get(args.parameters, 3))

        if order is None and args.default_order is not None:
            print(f"use default order \"{args.default_order}\"")
            order = args.default_order

        # get contract orders
        contract_orders = connection.get_contract_orders()

        # filer all sub-orders by contract orders (sub-orders assigned to employee)
        contract_suborders = connection.get_sub_orders() \
            .filter(
            lambda so:
            contract_orders
                .exists(
                    lambda o:
                        (o["order"]["number"] == so["order"]["number"]) and
                        (o["sub_order"]["number"] == so["number"])
                )
        )

        order = order.lower()

        matches = contract_suborders \
            .filter(
                lambda so:
                    (order in so["id"]) or
                    (order in so["short"].lower()) or
                    (order in so["order"]["number"].lower())
                ).to_list()

        if len(matches) == 0:
            raise RuntimeError(f"no order for {order} found")

        elif len(matches) > 1:
            raise RuntimeError(f"more than one order for {order} found")

        else:
            hours = int((duration * 60) / 60)
            minutes = int(duration * 60) - hours * 60
            connection.create_report(matches[0], comment, day.strftime("%Y-%m-%d"), hours, minutes)

            print(f"saved {hours}h and {minutes}min")


def command_config(args, config, file_name: str):
    if args.parameters[0] == "reset":
        os.remove(file_name)

    elif args.parameters[0] == "save":
        if args.user is not None:
            config.set(config.default_section, "user", args.user)

        if args.password is not None:
            base64_bytes = base64.b64encode(args.password.encode('ascii'))
            config.set(config.default_section, "password", base64_bytes.decode('ascii'))
            print(f"CAUTION: password saved in config file")

        if args.url is not None:
            config.set(config.default_section, "url", args.url)

        if args.default_order is not None:
            config.set(config.default_section, "default_order", args.default_order)

        with open(file_name, "w") as config_file:
            config.write(config_file)

    else:
        raise RuntimeError(f"unknown type {args.parameters[0]}")


if __name__ == "__main__":
    args = argparse.ArgumentParser(description='Python Salat')
    args.add_argument('command', type=str)
    args.add_argument('parameters', metavar='command parameters', type=str, nargs='+')
    args.add_argument('--url', dest='url', help='Salat URL')
    args.add_argument('--user', dest='user', help='Salat Username (Mitarbeiterkuerzel)', type=str)
    args.add_argument('--password', dest='password', help='Salat Passwort', type=str)
    args.add_argument('--summary', dest='summary', action="store_true", help='Summarize')
    args.add_argument('--verbose', dest='verbose', action="store_true", help='Verbose')
    args.add_argument('--config', dest='config', help='Name of config file', type=str)
    args.add_argument('--defaultorder', dest='default_order', help='Name of default order', type=str)
    args = args.parse_args()

    config_file_name = args.config if args.config is not None else Path.home().joinpath(".saladrc")
    config = load_config(args, config_file_name)

    try:
        if args.command == "config":
            command_config(args, config, config_file_name)
        elif args.command == "show":
            command_show(args)
        elif args.command == "report":
            command_report(args)
        else:
            raise RuntimeError(f"unknown command {args.command}")

        sys.exit(os.EX_OK)

    except RuntimeError as error:
        print(f"failed: {error}")
        sys.exit(os.EX_USAGE)
