import click
from pathlib import Path
import hashlib
import subprocess
import shutil
import requests
import json
from mimetypes import guess_extension


#  ──────────────────────────────────────────────────────────────────────────
# local imports

import bibtheque.functions as fn
import bibtheque.variables as v
import bibtheque.templates as temp


#  ──────────────────────────────────────────────────────────────────────────
# options

# tags
tags_option = click.option('-t', '--tags', type=str, default='', help="Comma delimited list of tags.")

# notes
notes_option = click.option('-n', '--notes', type=str, default='', help="Notes for the given document.")

# file options
file_option = click.option('-f', '--file', type=str, default=None, help="Document file.")
corrections_option = click.option('-c', '--corrections', type=str, default=None, help="Document file corrections.")
annotated_option = click.option('-a', '--annotated', type=str, default=None, help="Annotated document file.")
si_option = click.option('-si', '--supplemental', type=str, default=None, help="Supplemental information document file.")

# djvu flags
djvu_flag = click.option('--djvu', is_flag=True, default=False)
corrections_djvu_flag = click.option('--corrections-djvu', is_flag=True, default=False)
annotated_djvu_flag = click.option('--annotated-djvu', is_flag=True, default=False)
si_djvu_flag = click.option('--supplemental-djvu', is_flag=True, default=False)


#  ──────────────────────────────────────────────────────────────────────────
# base command

# done for the environment variables
@click.group(invoke_without_command=True, context_settings=v.CONTEXT_SETTINGS)
@click.version_option(v.bibtheque_version)
@click.option('--env', default='~/.config/bibtheque.env', type=str, help="Environment variable file path", show_default=True)
@click.pass_context
def bibtheque(ctx, env):
    """Simple and quick logging program."""

    ctx.ensure_object(dict)

    # pulling config from env file
    env = Path(env).expanduser()
    if env.exists():

        ctx.obj['env'] = {}

        with click.open_file(env, 'r') as file:
            for line in file.readlines():
                if line[0] not in ['#', '\n']:
                    line = line.split('=')
                    ctx.obj['env'][line[0]] = line[1].strip('\n')


        # database url
        base_url = ctx.obj['env']['IP'] + ":" + ctx.obj['env']['PORT']
        database_url = base_url + "/" + ctx.obj['env']['DATABASE']


        # authentication
        auth_url = ctx.obj['env']['COUCHDB_USER'] + ":" + ctx.obj['env']['COUCHDB_PASSWORD'] + "@"


        # secure http or not
        if int(ctx.obj['env']['SECURE']):
            prefix_url = "https://"
        else:
            prefix_url = "http://"



        # base url
        ctx.obj['base_url'] = prefix_url + base_url


        # full url
        ctx.obj['url'] = prefix_url + auth_url + database_url


        # local cache
        ctx.obj['cache'] = Path(ctx.obj['env']['CACHE']).expanduser()
        if not ctx.obj['cache'].exists():
            ctx.obj['cache'].mkdir(parents=True)


    else:
        raise click.ClickException('No environment variable file, exiting.')


#  ──────────────────────────────────────────────────────────────────────────
# insert 

@bibtheque.command('insert', help="Insert a new document with a given DOI.", short_help='Insert a new document.', context_settings=v.CONTEXT_SETTINGS)
@click.argument('doi', type=str)
@tags_option
@file_option
@corrections_option
@annotated_option
@si_option
@djvu_flag
@corrections_djvu_flag
@annotated_djvu_flag
@si_djvu_flag
@notes_option
@click.pass_context
def insert(ctx, doi, tags, file, corrections, annotated, supplemental, djvu, corrections_djvu, annotated_djvu, supplemental_djvu, notes):
    """Insert a document with a DOI."""

    # get bib for the doi
    bib = requests.get(doi, headers={"Accept": "application/x-bibtex"})
    bib = bib.content.lstrip()


    # convert the bib into json
    bibjson = subprocess.run(['pandoc', '-f', 'bibtex', '-t', 'csljson'], input=bib, capture_output=True)
    bibjson = json.loads(bibjson.stdout.decode('utf-8'))[0]
    bibjson['bib'] = bib.decode('utf-8').rstrip('\n')
    bibjson['year'] = str(bibjson['issued']['date-parts'][0][0])


    # handling and sanitizing tags
    bibjson['tags'] = ", ".join(sorted(set([t.strip() for t in tags.split(',')])))


    # handling notes
    bibjson['notes'] = notes


    # getting uuid from doi or couchdb
    if 'DOI' in bibjson:
        uuid = hashlib.sha256(bibjson['DOI'].encode()).hexdigest() # hashing because normal DOIs break the url
    else:
        uuid = requests.get(ctx.obj['url'] + '/_uuids')
        uuid = json.loads(uuid.content.decode('utf-8'))['uuids'][0]


    # inserting document
    fn.insert_document(ctx.obj['url'], uuid, bibjson, file, corrections, annotated, supplemental, djvu, corrections_djvu, annotated_djvu, supplemental_djvu)


    # print uuid for inserted document
    click.echo(click.style(uuid, bold=True))


#  ──────────────────────────────────────────────────────────────────────────
# manual

@bibtheque.command('manual', help="Manually add a document w/o a DOI", short_help="Add document w/o DOI.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('bibtype', type=click.Choice(list(temp.parent.keys())))
@click.option('--isbn', type=str, default=None, help="ISBN for the document")
@tags_option
@file_option
@corrections_option
@annotated_option
@si_option
@djvu_flag
@corrections_djvu_flag
@annotated_djvu_flag
@si_djvu_flag
@notes_option
@click.pass_context
def manual(ctx, bibtype, isbn, tags, file, corrections, annotated, supplemental, djvu, corrections_djvu, annotated_djvu, supplemental_djvu, notes):
    """Manually add a document that doesn't have a DOI."""

    # pull template
    doc = temp.doc | temp.parent[bibtype]


    # handling and sanitizing tags
    doc['tags'] = ", ".join(sorted(set([t.strip() for t in tags.split(',')])))


    # handling notes
    doc['notes'] = notes


    # edit fresh document
    doc = click.edit(json.dumps(doc, indent=4))

    if doc:
        doc = json.loads(doc)
        doc['bib'], doc_tmp = temp.make_bib(doc, bibtype)
        doc['type'] = bibtype
        doc['id'] = doc_tmp['id']

    else:
        raise click.ClickException("Nothing written!")


    # getting uuid from isbn or couchdb
    if isbn != None:
        doc['isbn'] = isbn.replace("-", "")
        uuid = hashlib.sha256(isbn.encode()).hexdigest() # hashing because normal DOIs break the url
        
    else:
        uuid = requests.get(ctx.obj['base_url'] + '/_uuids')
        uuid = json.loads(uuid.content.decode('utf-8'))['uuids'][0]


    # inserting document
    fn.insert_document(ctx.obj['url'], uuid, doc, file, corrections, annotated, supplemental, djvu, corrections_djvu, annotated_djvu, supplemental_djvu)


    # print uuid for inserted document
    click.echo(click.style(uuid, bold=True))


#  ──────────────────────────────────────────────────────────────────────────
# add

@bibtheque.command('add', help="Add to a document with the given ID.", short_help="Add to a document via ID.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('uuid', type=str)
@tags_option
@file_option
@corrections_option
@annotated_option
@si_option
@djvu_flag
@corrections_djvu_flag
@annotated_djvu_flag
@si_djvu_flag
@notes_option
@click.pass_context
def add(ctx, uuid, tags, file, corrections, annotated, supplemental, djvu, corrections_djvu, annotated_djvu, supplemental_djvu, notes):
    """Add the document of the given ID. file ands tags"""

    # modified flag
    modified = False


    # check doc exists
    doc = fn.document_exists(ctx.obj['url'], uuid)
    rev = doc["_rev"]


    # handling tags
    if len(tags) > 0:
        tags = [t.strip() for t in tags.split(',')]

        # only add if prior exist
        doc_tags = [t.strip() for t in doc['tags'].split(',') if len(t.strip()) > 0]
        if len(doc_tags) > 0:
            tags += doc_tags

        doc['tags'] = ", ".join(sorted(set([t.strip() for t in tags])))
        modified = True


    # handling notes
    if len(notes) > 0:
        doc['notes'] += ' ' + notes
        modified = True


    # update
    if modified:
        fn.modify_document(ctx.obj['url'], uuid, doc, "Update")


    # insert document files
    fn.add_files(ctx.obj['url'], uuid, file, corrections, annotated, supplemental, djvu, corrections_djvu, annotated_djvu, supplemental_djvu)


#  ──────────────────────────────────────────────────────────────────────────
# open

@bibtheque.command(name='open', help="Open the document file of the given ID", short_help="Open document via ID", context_settings=v.CONTEXT_SETTINGS)
@click.argument('uuid', type=str)
@click.option('-c', '--corrections', is_flag=True, default=False)
@click.option('-a', '--annotated', is_flag=True, default=False)
@click.option('-si', '--supplemental', is_flag=True, default=False)
@click.option('-su', '--show-url', is_flag=True, default=False, help="Show the database URL")
@click.option('-sp', '--show-path', is_flag=True, default=False, help="Show the cached path")
@click.pass_context
def open(ctx, uuid, corrections, annotated, supplemental, show_url, show_path):
    """Open the document at the uuid."""

    # check if document exists
    _ = fn.document_exists(ctx.obj['url'], uuid)

    # pulling proper document
    if corrections:
        file = "corrections"
    elif annotated:
        file = "annotated"
    elif supplemental:
        file = "supplemental"
    else:
        file = "document"


    # launching file viewer
    url, file_path = fn.download_file(ctx.obj['url'] + "/" + uuid + "/" + file, file, uuid, ctx.obj['cache'])
    click.launch(str(file_path))

    
    # showing locations
    if show_url:
        click.echo(url)

    if show_path:
        click.echo(file_path)


    return url, file_path


#  ──────────────────────────────────────────────────────────────────────────
# edit

@bibtheque.command('edit', help="Edit a document with the given ID.", short_help="Edit document via ID.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('uuid', type=str)
@click.pass_context
def edit(ctx, uuid):
    """Edit the document of the given ID."""

    # check if doc exists
    doc = fn.document_exists(ctx.obj['url'], uuid)

    # clean doc
    rev = doc["_rev"]
    del doc["_id"]
    del doc["_rev"]


    # edit document
    updated = click.edit(json.dumps(doc, indent=4))

    if updated:
        updated = json.loads(updated)
        updated['_rev'] = rev

    else:
        raise click.ClickException("No edits written!")

    
    # update
    fn.modify_document(ctx.obj['url'], uuid, updated, "Update")

    return updated


#  ──────────────────────────────────────────────────────────────────────────
# delete

@bibtheque.command('delete', help="Delete a document with the given ID.", short_help="Delete document via ID.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('uuid', type=str)
@click.pass_context
def delete(ctx, uuid):
    """Delete the document of the given ID."""

    # check if document exists
    doc = fn.document_exists(ctx.obj['url'], uuid)

    # confirm deletion
    click.confirm(click.style("BibTeX contents are:\n", bold=True) + json.dumps(json.loads(req.content)['docs'][0]['bib']) + "\n" + "Are you sure you want to delete " + click.style(uuid, bold=True), abort=True)
    fn.delete_document(ctx.obj['url'], uuid)



#  ──────────────────────────────────────────────────────────────────────────
# search

@bibtheque.command('search', help="Search documents with a regular expression.", short_help="Search with regex.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('text', type=str)
@click.option('-f', '--field', type=str, default='bib', help="Field to search through; default: 'bib'")
@click.option('-sfs', '--show-fields', type=str, default='_id, bib, title, author, year', help="Comma delimited list of fields to show; default: 'bib'")
@click.option('-t', '--tags', is_flag=True, type=bool, default=False, help="Just search the tags; default: False")
@click.option('-b', '--bib', is_flag=True, type=bool, default=False, help="Show only the 'bib'; default: False")
@click.option('-j', '--json', '_json', is_flag=True, type=bool, default=False, help="Print as json string; default: False")
@click.option('-o', '--open', '_open', type=int, default=None, help="Open selected document.")
@click.option('-e', '--edit', '_edit', type=int, default=None, help="Edit selected document.")
@click.option('-p', '--pipe', '_pipe', type=int, default=None, help="Pipe selected document ID.")
@click.pass_context
def search(ctx, text, field, show_fields, tags, bib, _json, _open, _edit, _pipe):
    """Search with the document database with a regular expression."""

    # handling tags flag
    if tags:
        field = "tags"


    # separating the fields to show
    show_fields = [x.strip() for x in show_fields.split(',') if len(x.strip()) > 0]


    # requesting search
    search_json = {"selector": {field: {"$regex": text}},
                   "fields": show_fields,}
    content = fn.search_document(ctx.obj['url'], search_json)


    # printing
    # open
    if _open:
        for i, doc in enumerate(content['docs']):
            if _open == i+1:
                # calling open command
                ctx.invoke(open, uuid=doc['_id'], corrections=False, annotated=False, show_url=False, show_path=False)

    # edit
    if _edit:
        for i, doc in enumerate(content['docs']):
            if _edit == i+1:
                # calling edit command
                ctx.invoke(edit, uuid=doc['_id'])


    # pipe
    if _pipe:
        for i, doc in enumerate(content['docs']):
            if _pipe == i+1:
                # echoing to stdout
                click.echo(doc['_id'])


    # bib - FIX need to handle overlapping ids in the bib
    elif bib:
        for doc in content['docs']:
            click.echo(doc['bib'])


    # json
    elif _json:
        click.echo(json.dumps(content['docs'], indent=4))


    # default
    else:
        for i, doc in enumerate(content['docs']):
            click.echo(click.style('[' + str(i+1) + '] ' + doc['_id'], bold=True))
            click.echo(doc['title'])
            click.echo("(" + doc['year'] + ") " + click.style(temp.make_bib_author(doc['author']), italic=True))


#  ──────────────────────────────────────────────────────────────────────────
# cache

@bibtheque.command('cache', help="Manage the local document cache.", short_help="Manage local cache.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('clean', type=bool, default=False)
@click.pass_context
def cache(ctx, clean):
    """Manage the local cache of document files."""

    if clean:
        click.confirm(click.style("Are you sure you want to delete the cache?", bold=True), abort=True)
        # delete
        shutil.rmtree(ctx.obj['cache'])

        # remake
        ctx.obj['cache'].mkdir(parents=True)


#  ──────────────────────────────────────────────────────────────────────────
# check

@bibtheque.command('check', help="Retrieve the BibTeX information for the given DOI.", short_help="Get BibTeX info from DOI.", context_settings=v.CONTEXT_SETTINGS)
@click.argument('doi', type=str)
@click.pass_context
def cache(ctx, doi):
    """Check BibTeX information for the given DOI"""

    # get bib for the doi
    bib = requests.get(doi, headers={"Accept": "application/x-bibtex"})
    bib = bib.content.strip()

    # print
    click.echo(bib)
