"""Export ship loadout in ED Shipyard plain text format."""
from __future__ import annotations

import json
import os
import pathlib
import re
import time
from collections import defaultdict
from typing import Union

import outfitting
import util_ships
from config import config
from edmc_data import edshipyard_slot_map as slot_map
from edmc_data import ship_name_map
from EDMCLogging import get_main_logger

logger = get_main_logger()

__Module = dict[str, Union[str, list[str]]]  # Have to keep old-style here for compatibility

# Map API ship names to ED Shipyard names
ship_map = ship_name_map.copy()

# Ship masses
ships_file = config.respath_path / "ships.json"
with open(ships_file, encoding="utf-8") as ships_file_handle:
    ships = json.load(ships_file_handle)


def export(data, filename=None) -> None:  # noqa: C901, CCR001
    """
    Export ship loadout in E:D Shipyard plain text format.

    :param data: CAPI data.
    :param filename: Override default file name.
    """
    def class_rating(module: __Module) -> str:
        """
        Return a string representation of the class of the given module.

        :param module: Module data dict.
        :return: Rating of the module.
        """
        mod_class = module['class']
        mod_rating = module['rating']
        mod_mount = module.get('mount')
        mod_guidance: str = str(module.get('guidance'))

        ret = f'{mod_class}{mod_rating}'
        if 'guidance' in module:  # Missiles
            if mod_mount is not None:
                mount = mod_mount[0]

            else:
                mount = 'F'

            guidance = mod_guidance[0]
            ret += f'/{mount}{guidance}'

        elif 'mount' in module:  # Hardpoints
            ret += f'/{mod_mount}'

        elif 'Cabin' in module['name']:  # Passenger cabins
            ret += f'/{module["name"][0]}'

        return ret + ' '

    querytime = config.get_int('querytime', default=int(time.time()))

    loadout = defaultdict(list)
    mass = 0.0
    fuel = 0
    cargo = 0
    fsd = None
    jumpboost = 0

    for slot in sorted(data['ship']['modules']):
        v = data['ship']['modules'][slot]
        try:
            if not v:
                continue

            module: __Module | None = outfitting.lookup(v['module'], ship_map)
            if not module:
                continue

            cr = class_rating(module)
            mods = v.get('modifications') or v.get('WorkInProgress_modifications') or {}
            if mods.get('OutfittingFieldType_Mass'):
                mass += float(module.get('mass', 0.0) * mods['OutfittingFieldType_Mass']['value'])

            else:
                mass += float(module.get('mass', 0.0))  # type: ignore

            # Specials
            if 'Fuel Tank' in module['name']:
                fuel += 2**int(module['class'])  # type: ignore
                name = f'{module["name"]} (Capacity: {2**int(module["class"])})'  # type: ignore

            elif 'Cargo Rack' in module['name']:
                cargo += 2**int(module['class'])  # type: ignore
                name = f'{module["name"]} (Capacity: {2**int(module["class"])})'  # type: ignore

            else:
                name = module['name']  # type: ignore

            if name == 'Frame Shift Drive' or name == 'Frame Shift Drive (SCO)':
                fsd = module  # save for range calculation

                if mods.get('OutfittingFieldType_FSDOptimalMass'):
                    fsd['optmass'] *= mods['OutfittingFieldType_FSDOptimalMass']['value']

                if mods.get('OutfittingFieldType_MaxFuelPerJump'):
                    fsd['maxfuel'] *= mods['OutfittingFieldType_MaxFuelPerJump']['value']

            jumpboost += module.get('jumpboost', 0)  # type: ignore

            for slot_prefix, index in slot_map.items():
                if slot.lower().startswith(slot_prefix):
                    loadout[index].append(cr + name)
                    break

            else:
                if slot.lower().startswith('slot'):
                    loadout[slot[-1]].append(cr + name)
                elif not slot.lower().startswith('planetaryapproachsuite'):
                    logger.debug(f'EDShipyard: Unknown slot {slot}')

        except AssertionError as e:
            logger.debug(f'EDShipyard: {e!r}')
            continue  # Silently skip unrecognized modules

        except Exception:
            if __debug__:
                raise

    # Construct description
    ship = ship_map.get(data['ship']['name'].lower(), data['ship']['name'])
    if data['ship'].get('shipName') is not None:
        _ships = f'{ship}, {data["ship"]["shipName"]}'

    else:
        _ships = ship

    string = f'[{_ships}]\n'

    slot_types = (
        'H', 'L', 'M', 'S', 'U', None, 'BH', 'RB', 'TM', 'FH', 'EC', 'PC', 'SS', 'FS', None, 'MC', None, '9', '8',
        '7', '6', '5', '4', '3', '2', '1'
    )
    for slot in slot_types:
        if not slot:
            string += '\n'

        elif slot in loadout:
            for name in loadout[slot]:
                string += f'{slot}: {name}\n'

    string += f'---\nCargo : {cargo} T\nFuel  : {fuel} T\n'

    # Add mass and range
    assert data['ship']['name'].lower() in ship_name_map, data['ship']['name']
    assert ship_name_map[data['ship']['name'].lower()] in ships, ship_name_map[data['ship']['name'].lower()]

    try:
        mass += ships[ship_name_map[data['ship']['name'].lower()]]['hullMass']
        string += f'Mass  : {mass:.2f} T empty\n        {mass + fuel + cargo:.2f} T full\n'
        maxfuel = fsd.get('maxfuel', 0)  # type: ignore
        fuelmul = fsd.get('fuelmul', 0)  # type: ignore

        try:
            multiplier = pow(min(fuel, maxfuel) / fuelmul, 1.0 / fsd['fuelpower']) * fsd['optmass']  # type: ignore
            range_unladen = multiplier / (mass + fuel) + jumpboost
            range_laden = multiplier / (mass + fuel + cargo) + jumpboost
            # As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with
            # a locale that uses comma for decimal separator.
        except ZeroDivisionError:
            range_unladen = range_laden = 0.0
        string += (f'Range : {range_unladen:.2f} LY unladen\n'
                   f'        {range_laden:.2f} LY laden\n')

    except Exception:
        if __debug__:
            raise

    if filename:
        with open(filename, 'wt') as h:
            h.write(string)
        return

    # Look for last ship of this type
    ship = util_ships.ship_file_name(data['ship'].get('shipName'), data['ship']['name'])
    regexp = re.compile(re.escape(ship) + r'\.\d{4}-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt')
    oldfiles = sorted([x for x in os.listdir(config.get_str('outdir')) if regexp.match(x)])
    if oldfiles:
        with (pathlib.Path(config.get_str('outdir')) / oldfiles[-1]).open() as h:
            if h.read() == string:
                return  # same as last time - don't write

    # Write
    timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
    filename = pathlib.Path(config.get_str('outdir')) / f'{ship}.{timestamp}.txt'

    with open(filename, 'wt') as h:
        h.write(string)