"""
Handles functionality related to data storege.
"""
import sys, os, glob, re, gzip, json
from biorun import const, utils, objects, ncbi
from biorun.models import jsonrec
import biorun.libs.placlib as plac

# Module level logger.
logger = utils.logger

# A nicer error message on incorrect installation.
try:
    from Bio import SeqIO
except ImportError as exc:
    print(f"*** Error: {exc}", file=sys.stderr)
    print(f"*** This program requires biopython", file=sys.stderr)
    print(f"*** Install: conda install -y biopython>=1.78", file=sys.stderr)
    sys.exit(-1)

def resolve_fname(name, format='json'):
    """
    Resolve a file name given an accession number.
    """
    ext = format.lower()
    fname = f"{name}.{ext}.gz"
    fname = os.path.join(utils.DATADIR, fname)
    return fname


def delete_data(text):
    """
    Deletes data under a filename.
    """
    for name in text.split(","):
        fname = resolve_fname(name)
        if os.path.isfile(fname):
            os.remove(fname)
            logger.info(f"removed: {fname}")
        else:
            logger.info(f"file does not exist: {fname}")


def read_json_file(fname):
    """
    Returns the content of a JSON file.
    """
    fp = utils.gz_read(fname)
    data = json.load(fp)
    fp.close()
    return data


def save_json_file(fname, data):
    """
    Returns the content of a JSON file.
    """
    fp = utils.gz_write(fname)
    json.dump(data, fp)
    fp.close()
    logger.info(f"saved {fname}")
    return data


def change_seqid(json_name, seqid):
    """
    Changes the sequence id stored in a json file.
    """
    if os.path.isfile(json_name):
        data = read_json_file(json_name)
        for item in data:
            item[const.SEQID] = seqid
        fp = utils.gz_write(json_name)
        json.dump(data, fp)
        fp.close()


def fetch_data(data, param):
    """
    Obtains data from NCBI. Fills each parameter with a json field.
    """

    db = "protein" if param.protein else "nuccore"

    # Ensure json DB is built
    ncbi.build_db()

    genbank, taxon_acc, refseq = ncbi.get_data()

    for name in data:

        # Pretend no data if it is an update.
        json = None if param.update else get_json(name)

        # The data exists, nothing needs to be done.
        if json:
            continue

        # The JSON representation of the data.
        json_name = resolve_fname(name=name, format="json")

        # GenBank representation of the data.
        gbk_name = resolve_fname(name=name, format="gb")

        # Genome assembly data.
        if name.startswith("GCA") or name.startswith("GCF"):

            ncbi.genome(name=name, fname=gbk_name, update=param.update, genbank=genbank,
                        refseq=refseq)
        else:
            # Genbank data.
            ncbi.genbank_save(name, db=db, fname=gbk_name)

        # Convert Genbank to JSON.
        data = jsonrec.parse_file(fname=gbk_name, seqid=param.seqid)

        # Save JSON file.
        save_json_file(fname=json_name, data=data)


def genbank_view(params):
    for param in params:
        altname = resolve_fname(param.acc, format="gb")

        if os.path.isfile(param.acc):
            stream = utils.gz_read(param.acc)
        elif os.path.isfile(altname):
            stream = utils.gz_read(altname)
        else:
            stream = []
            utils.error(f"data not found: {param.acc}")

        for line in stream:
            print(line, end='')


def get_json(name, seqid=None, inter=False, strict=False):
    """
    Attempts to return a JSON formatted data based on a name.
    """

    # Data is an existing path to a JSON file.
    if os.path.isfile(name):
        try:
            data = jsonrec.parse_file(name, seqid=seqid)
        except Exception as exc:
            logger.error(f"JSON parsing error for file {name}: {exc}")
            sys.exit(-1)
        return data

    # The JSON representation of the data.
    json_name = resolve_fname(name=name, format="json")

    # GenBank representation of the data.
    gbk_name = resolve_fname(name=name, format="gb")

    # Found the JSON representation of the file.
    if os.path.isfile(json_name):
        logger.info(f"found {json_name}")
        data = read_json_file(json_name)
        return data

    # There is no JSON file but there is a GenBank file.
    if os.path.isfile(gbk_name):
        logger.info(f"found {gbk_name}")
        data = jsonrec.parse_file(fname=gbk_name, seqid=seqid)
        data = save_json_file(fname=json_name, data=data)
        return data

    # Interactive input, make JSON from name
    if inter:
        data = jsonrec.make_jsonrec(name, seqid=seqid)
        return data

    # Raise error if in strict mode
    if strict:
        utils.error(f"data not found: {name}")
    return None


def rename_data(data, param, newname=None):
    """
    Rename data.
    """
    # Will only rename a single data
    newnames = newname.split(",")

    for name1, name2 in zip(data, newnames):
        src_json = resolve_fname(name=name1, format="json")
        dest_json = resolve_fname(name=name2, format="json")

        src_gb = resolve_fname(name=name1, format="gb")
        dest_gb = resolve_fname(name=name2, format="gb")

        if os.path.isfile(src_json):
            logger.info(f"renamed {name1} as {name2}")
            os.rename(src_json, dest_json)
            if param.seqid:
                change_seqid(dest_json, seqid=param.seqid)
        else:
            logger.info(f"file not found: {src_json}")

        if os.path.isfile(src_gb):
            if not os.path.isfile(dest_gb):
                os.symlink(src_gb, dest_gb)
        else:
            logger.info(f"file not found: {src_gb}")


def print_data_list():
    """
    Returns a list of the files in the data directory
    """
    pattern = os.path.join(os.path.join(utils.DATADIR, '*.json.gz'))
    matched = glob.glob(pattern)

    # Extract the definition from the JSON without parsing it.
    patt = re.compile(r'(definition\":\s*)(?P<value>\".+?\")')
    collect = []

    for path in matched:
        fsize = utils.human_size(os.path.getsize(path))
        base, fname = os.path.split(path)
        fname = fname.rsplit(".", maxsplit=2)[0]

        # Parse the first N lines
        stream = gzip.open(path, 'rt') if path.endswith('gz') else open(path, 'rt')
        text = stream.read(1000)
        match = patt.search(text)

        title = match.group("value") if match else ''
        title = title.strip('", ')

        # Trim the title
        stitle = title[:100]
        stitle = stitle + "..." if len(title) != len(stitle) else stitle

        collect.append((str(fsize), f"{fname:10s}", stitle))

    collect = sorted(collect, key=lambda x: x[2])
    for row in collect:
        line = "\t".join(row)
        print(line)


@plac.pos("data", "data names")
@plac.flg('fetch', "download data as accessions")
@plac.flg('update', "updates data in storage")
@plac.opt('rename', "rename the data")
@plac.opt('seqid', "set the sequence id of the data")
@plac.flg('protein', "use the protein database")
@plac.flg('build', "build the database")
@plac.flg('verbose', "verbose mode")
def run(update=False, rename='', seqid='', protein=False, verbose=False, *data):
    """
    Fetches and manages data in storage.
    """

    # Set the verbosity
    utils.set_verbosity(logger, level=int(verbose))

    # Reset counter (needed for consistency during testing).
    jsonrec.reset_counter()

    # A simple wrapper class to represent input parameters.
    param = objects.Param(seqid=seqid, rename=rename, start=1, protein=protein, update=update)

    # Fetch the data.
    fetch_data(data, param=param)

    # Renaming after fetching.
    if rename:
        rename_data(data, param=param, newname=rename)


@plac.opt('delete', "deletes foo from storage", metavar='foo')
@plac.flg('verbose', "verbose mode")
def data(delete, verbose=False):
    """
    Shows the data in the storage.

    Usage:

        bio data                   : lists the data
        bio data --delete foo      : deletes data called foo
        bio data --delete foo,bar  : deletes multiple datasets
    """
    # Set the verbosity
    utils.set_verbosity(logger, level=int(verbose))

    # Reset counter (needed for consistency during testing).
    jsonrec.reset_counter()

    # Delete should be the first to execute.
    if delete:
        delete_data(delete)
    else:
        # Prints the data listing.
        print_data_list()
[(246, 'biorun.libs.placlib.pos', 'plac.pos', (['"""data"""', '"""data names"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (247, 'biorun.libs.placlib.flg', 'plac.flg', (['"""fetch"""', '"""download data as accessions"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (248, 'biorun.libs.placlib.flg', 'plac.flg', (['"""update"""', '"""updates data in storage"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (249, 'biorun.libs.placlib.opt', 'plac.opt', (['"""rename"""', '"""rename the data"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (250, 'biorun.libs.placlib.opt', 'plac.opt', (['"""seqid"""', '"""set the sequence id of the data"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (251, 'biorun.libs.placlib.flg', 'plac.flg', (['"""protein"""', '"""use the protein database"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (252, 'biorun.libs.placlib.flg', 'plac.flg', (['"""build"""', '"""build the database"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (253, 'biorun.libs.placlib.flg', 'plac.flg', (['"""verbose"""', '"""verbose mode"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (276, 'biorun.libs.placlib.opt', 'plac.opt', (['"""delete"""', '"""deletes foo from storage"""'], {'metavar': '"""foo"""'}), True, 'import biorun.libs.placlib as plac\n'), (277, 'biorun.libs.placlib.flg', 'plac.flg', (['"""verbose"""', '"""verbose mode"""'], {}), True, 'import biorun.libs.placlib as plac\n'), (27, 'os.path.join', 'os.path.join', (['utils.DATADIR', 'fname'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (48, 'biorun.utils.gz_read', 'utils.gz_read', (['fname'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (49, 'json.load', 'json.load', (['fp'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (58, 'biorun.utils.gz_write', 'utils.gz_write', (['fname'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (59, 'json.dump', 'json.dump', (['data', 'fp'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (69, 'os.path.isfile', 'os.path.isfile', (['json_name'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (86, 'biorun.ncbi.build_db', 'ncbi.build_db', ([], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (88, 'biorun.ncbi.get_data', 'ncbi.get_data', ([], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (143, 'os.path.isfile', 'os.path.isfile', (['name'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (158, 'os.path.isfile', 'os.path.isfile', (['json_name'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (164, 'os.path.isfile', 'os.path.isfile', (['gbk_name'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (215, 'glob.glob', 'glob.glob', (['pattern'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (218, 're.compile', 're.compile', (['"""(definition\\\\":\\\\s*)(?P<value>\\\\".+?\\\\")"""'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (263, 'biorun.models.jsonrec.reset_counter', 'jsonrec.reset_counter', ([], {}), False, 'from biorun.models import jsonrec\n'), (266, 'biorun.objects.Param', 'objects.Param', ([], {'seqid': 'seqid', 'rename': 'rename', 'start': '(1)', 'protein': 'protein', 'update': 'update'}), False, 'from biorun import const, utils, objects, ncbi\n'), (292, 'biorun.models.jsonrec.reset_counter', 'jsonrec.reset_counter', ([], {}), False, 'from biorun.models import jsonrec\n'), (19, 'sys.exit', 'sys.exit', (['(-1)'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (37, 'os.path.isfile', 'os.path.isfile', (['fname'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (73, 'biorun.utils.gz_write', 'utils.gz_write', (['json_name'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (74, 'json.dump', 'json.dump', (['data', 'fp'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (115, 'biorun.models.jsonrec.parse_file', 'jsonrec.parse_file', ([], {'fname': 'gbk_name', 'seqid': 'param.seqid'}), False, 'from biorun.models import jsonrec\n'), (125, 'os.path.isfile', 'os.path.isfile', (['param.acc'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (166, 'biorun.models.jsonrec.parse_file', 'jsonrec.parse_file', ([], {'fname': 'gbk_name', 'seqid': 'seqid'}), False, 'from biorun.models import jsonrec\n'), (172, 'biorun.models.jsonrec.make_jsonrec', 'jsonrec.make_jsonrec', (['name'], {'seqid': 'seqid'}), False, 'from biorun.models import jsonrec\n'), (177, 'biorun.utils.error', 'utils.error', (['f"""data not found: {name}"""'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (195, 'os.path.isfile', 'os.path.isfile', (['src_json'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (203, 'os.path.isfile', 'os.path.isfile', (['src_gb'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (214, 'os.path.join', 'os.path.join', (['utils.DATADIR', '"""*.json.gz"""'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (223, 'os.path.split', 'os.path.split', (['path'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (38, 'os.remove', 'os.remove', (['fname'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (108, 'biorun.ncbi.genome', 'ncbi.genome', ([], {'name': 'name', 'fname': 'gbk_name', 'update': 'param.update', 'genbank': 'genbank', 'refseq': 'refseq'}), False, 'from biorun import const, utils, objects, ncbi\n'), (112, 'biorun.ncbi.genbank_save', 'ncbi.genbank_save', (['name'], {'db': 'db', 'fname': 'gbk_name'}), False, 'from biorun import const, utils, objects, ncbi\n'), (126, 'biorun.utils.gz_read', 'utils.gz_read', (['param.acc'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (127, 'os.path.isfile', 'os.path.isfile', (['altname'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (145, 'biorun.models.jsonrec.parse_file', 'jsonrec.parse_file', (['name'], {'seqid': 'seqid'}), False, 'from biorun.models import jsonrec\n'), (197, 'os.rename', 'os.rename', (['src_json', 'dest_json'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (222, 'os.path.getsize', 'os.path.getsize', (['path'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (227, 'gzip.open', 'gzip.open', (['path', '"""rt"""'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (128, 'biorun.utils.gz_read', 'utils.gz_read', (['altname'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (131, 'biorun.utils.error', 'utils.error', (['f"""data not found: {param.acc}"""'], {}), False, 'from biorun import const, utils, objects, ncbi\n'), (148, 'sys.exit', 'sys.exit', (['(-1)'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (204, 'os.path.isfile', 'os.path.isfile', (['dest_gb'], {}), False, 'import sys, os, glob, re, gzip, json\n'), (205, 'os.symlink', 'os.symlink', (['src_gb', 'dest_gb'], {}), False, 'import sys, os, glob, re, gzip, json\n')]