#!/usr/bin/env python3
#
# Command-line interface. Requires prior setup through the GUI.
#

import argparse
import json
import sys
import os
from typing import Any, Optional

# workaround for https://github.com/EDCD/EDMarketConnector/issues/568
os.environ["EDMC_NO_UI"] = "1"

from os.path import getmtime, join
from time import time, sleep
import re

import l10n
l10n.Translations.install_dummy()

import collate
import companion
import commodity
from commodity import COMMODITY_DEFAULT
import outfitting
import loadout
import edshipyard
import shipyard
import stats
from config import appcmdname, appversion, config
from update import Updater, EDMCVersion
from monitor import monitor

sys.path.append(config.internal_plugin_dir)
import eddn


SERVER_RETRY = 5  # retry pause for Companion servers [s]
EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_LAGGING, EXIT_SYS_ERR = range(6)

JOURNAL_RE = re.compile(r'^Journal(Beta)?\.[0-9]{12}\.[0-9]{2}\.log$')


# quick and dirty version comparison assuming "strict" numeric only version numbers
def versioncmp(versionstring):
    return list(map(int, versionstring.split('.')))


def deep_get(target: dict, *args: str, default=None) -> Any:
    if not hasattr(target, 'get'):
        raise ValueError(f"Cannot call get on {target} ({type(target)})")

    current = target
    for arg in args:
        res = current.get(arg)
        if res is None:
            return default

        current = res

    return current


def main():
    try:
        # arg parsing
        parser = argparse.ArgumentParser(
            prog=appcmdname,
            description='Prints the current system and station (if docked) to stdout and optionally writes player '
                        'status, ship locations, ship loadout and/or station data to file. '
                        'Requires prior setup through the accompanying GUI app.'
        )

        parser.add_argument('-v', '--version', help='print program version and exit', action='store_const', const=True)
        parser.add_argument('-a', metavar='FILE', help='write ship loadout to FILE in Companion API json format')
        parser.add_argument('-e', metavar='FILE', help='write ship loadout to FILE in E:D Shipyard plain text format')
        parser.add_argument('-l', metavar='FILE', help='write ship locations to FILE in CSV format')
        parser.add_argument('-m', metavar='FILE', help='write station commodity market data to FILE in CSV format')
        parser.add_argument('-o', metavar='FILE', help='write station outfitting data to FILE in CSV format')
        parser.add_argument('-s', metavar='FILE', help='write station shipyard data to FILE in CSV format')
        parser.add_argument('-t', metavar='FILE', help='write player status to FILE in CSV format')
        parser.add_argument('-d', metavar='FILE', help='write raw JSON data to FILE')
        parser.add_argument('-n', action='store_true', help='send data to EDDN')
        parser.add_argument('-p', metavar='CMDR', help='Returns data from the specified player account')
        parser.add_argument('-j', help=argparse.SUPPRESS)  # Import JSON dump
        args = parser.parse_args()

        if args.version:
            updater = Updater(provider='internal')
            newversion: Optional[EDMCVersion] = updater.check_appcast()
            if newversion:
                print(f'{appversion} ({newversion.title!r} is available)')
            else:
                print(appversion)
            sys.exit(EXIT_SUCCESS)

        if args.j:
            # Import and collate from JSON dump
            data = json.load(open(args.j))
            config.set('querytime', int(getmtime(args.j)))

        else:
            # Get state from latest Journal file
            try:
                logdir = config.get('journaldir') or config.default_journal_dir
                logfiles = sorted((x for x in os.listdir(logdir) if JOURNAL_RE.search(x)), key=lambda x: x.split('.')[1:])

                logfile = join(logdir, logfiles[-1])

                with open(logfile, 'r') as loghandle:
                    for line in loghandle:
                        try:
                            monitor.parse_entry(line)
                        except Exception:
                            if __debug__:
                                print(f'Invalid journal entry {line!r}')

            except Exception as e:
                print(f"Can't read Journal file: {str(e)}", file=sys.stderr)
                sys.exit(EXIT_SYS_ERR)

            if not monitor.cmdr:
                print('Not available while E:D is at the main menu', file=sys.stderr)
                sys.exit(EXIT_SYS_ERR)

            # Get data from Companion API
            if args.p:
                cmdrs = config.get('cmdrs') or []
                if args.p in cmdrs:
                    idx = cmdrs.index(args.p)

                else:
                    for idx, cmdr in enumerate(cmdrs):
                        if cmdr.lower() == args.p.lower():
                            break

                    else:
                        raise companion.CredentialsError()

                companion.session.login(cmdrs[idx], monitor.is_beta)

            else:
                cmdrs = config.get('cmdrs') or []
                if monitor.cmdr not in cmdrs:
                    raise companion.CredentialsError()

                companion.session.login(monitor.cmdr, monitor.is_beta)

            querytime = int(time())
            data = companion.session.station()
            config.set('querytime', querytime)

        # Validation
        if not deep_get(data, 'commander', 'name', default='').strip():
            print('Who are you?!', file=sys.stderr)
            sys.exit(EXIT_SERVER)

        elif not deep_get(data, 'lastSystem', 'name') or \
                data['commander'].get('docked') and not \
                deep_get(data, 'lastStarport', 'name'):  # Only care if docked

            print('Where are you?!', file=sys.stderr)  # Shouldn't happen
            sys.exit(EXIT_SERVER)

        elif not deep_get(data, 'ship', 'modules') or not deep_get(data, 'ship', 'name', default=''):
            print('What are you flying?!', file=sys.stderr)  # Shouldn't happen
            sys.exit(EXIT_SERVER)

        elif args.j:
            pass  # Skip further validation

        elif data['commander']['name'] != monitor.cmdr:
            print('Wrong Cmdr', file=sys.stderr)  # Companion API return doesn't match Journal
            sys.exit(EXIT_CREDENTIALS)

        elif data['lastSystem']['name'] != monitor.system or \
                ((data['commander']['docked'] and data['lastStarport']['name'] or None) != monitor.station) or \
                data['ship']['id'] != monitor.state['ShipID'] or \
                data['ship']['name'].lower() != monitor.state['ShipType']:

            print('Frontier server is lagging', file=sys.stderr)
            sys.exit(EXIT_LAGGING)

        # stuff we can do when not docked
        if args.d:
            out = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': '))
            with open(args.d, 'wb') as f:
                f.write(out.encode("utf-8"))

        if args.a:
            loadout.export(data, args.a)

        if args.e:
            edshipyard.export(data, args.e)

        if args.l:
            stats.export_ships(data, args.l)

        if args.t:
            stats.export_status(data, args.t)

        if data['commander'].get('docked'):
            print('{},{}'.format(
                deep_get(data, 'lastSystem', 'name', default='Unknown'),
                deep_get(data, 'lastStarport', 'name', default='Unknown')
            ))

        else:
            print(deep_get(data, 'lastSystem', 'name', default='Unknown'))

        if (args.m or args.o or args.s or args.n or args.j):
            if not data['commander'].get('docked'):
                print("You're not docked at a station!", file=sys.stderr)
                sys.exit(EXIT_SUCCESS)

            elif not deep_get(data, 'lastStarport', 'name'):
                print("Unknown station!", file=sys.stderr)
                sys.exit(EXIT_LAGGING)

            # Ignore possibly missing shipyard info
            elif not data['lastStarport'].get('commodities') or data['lastStarport'].get('modules'):
                print("Station doesn't have anything!", file=sys.stderr)
                sys.exit(EXIT_SUCCESS)

        else:
            sys.exit(EXIT_SUCCESS)

        # Finally - the data looks sane and we're docked at a station

        if args.j:
            # Collate from JSON dump
            collate.addcommodities(data)
            collate.addmodules(data)
            collate.addships(data)

        if args.m:
            if data['lastStarport'].get('commodities'):
                # Fixup anomalies in the commodity data
                fixed = companion.fixup(data)
                commodity.export(fixed, COMMODITY_DEFAULT, args.m)

            else:
                print("Station doesn't have a market", file=sys.stderr)

        if args.o:
            if data['lastStarport'].get('modules'):
                outfitting.export(data, args.o)

            else:
                print("Station doesn't supply outfitting", file=sys.stderr)

        if (args.s or args.n) and not args.j and not \
                data['lastStarport'].get('ships') and data['lastStarport']['services'].get('shipyard'):

            # Retry for shipyard
            sleep(SERVER_RETRY)
            new_data = companion.session.station()
            # might have undocked while we were waiting for retry in which case station data is unreliable
            if new_data['commander'].get('docked') and \
                    deep_get(new_data, 'lastSystem', 'name') == monitor.system and \
                    deep_get(new_data, 'lastStarport', 'name') == monitor.station:

                data = new_data

        if args.s:
            if deep_get(data, 'lastStarport', 'ships', 'shipyard_list'):
                shipyard.export(data, args.s)

            elif not args.j and monitor.stationservices and 'Shipyard' in monitor.stationservices:
                print("Failed to get shipyard data", file=sys.stderr)

            else:
                print("Station doesn't have a shipyard", file=sys.stderr)

        if args.n:
            try:
                eddn_sender = eddn.EDDN(None)
                eddn_sender.export_commodities(data, monitor.is_beta)
                eddn_sender.export_outfitting(data, monitor.is_beta)
                eddn_sender.export_shipyard(data, monitor.is_beta)

            except Exception as e:
                print(f"Failed to send data to EDDN: {str(e)}", file=sys.stderr)

        sys.exit(EXIT_SUCCESS)

    except companion.ServerError:
        print('Server is down', file=sys.stderr)
        sys.exit(EXIT_SERVER)

    except companion.SKUError:
        print('Server SKU problem', file=sys.stderr)
        sys.exit(EXIT_SERVER)

    except companion.CredentialsError:
        print('Invalid Credentials', file=sys.stderr)
        sys.exit(EXIT_CREDENTIALS)


if __name__ == '__main__':
    main()