#!/usr/bin/env python3

"""Create subpages for published experiments to a Grav CMS repository.

Description
-----------

This script creates subpages for an experiments page from experiments from a collection on Zenodo or published
elsewhere with a DOI. It works together with https://git.opencarp.org/infrastructure/opencarp-grav-experiments-plugin
to create an "experiments marketplace".

The page need to be already existing in Grav and contain a ``pipeline`` field in their frontmatter. The script will
find all pages which match the provided PIPELINE and will add subpages to this page. The experiments themselves are
configured via the `experiments` object in the frontmatter of the page, e.g.:

```yaml
experiments:
    locations:
    - 'https://zenodo.org/api/communities/opencarp/records'
    gitlab_groups:
    - 'https://git.opencarp.org/api/v4/groups/153'
    dois:
    - 10.35097/763
    - 10.35097/606
    - 10.35097/580
    - 10.35097/1830
    - 10.35097/qec954fvka86gcjw
```

Please refer to https://opencarp.org/community/community-experiments for an example setup.

Usage
-----

.. argparse::
    :module: facile_rs.run_docstring_pipeline
    :func: create_parser
    :prog: run_docstring_pipeline.py

"""
import datetime
import logging
import re
import shutil

import frontmatter
from slugify import slugify

from .utils import cli
from .utils.grav import collect_pages
from .utils.http import fetch_dict

logger = logging.getLogger(__file__)

DATACITE_URL = 'https://api.datacite.org/dois/'
DATACITE_HEADERS = {
    'User-Agent': 'FACILE-RS/1.0 (https://git.opencarp.org/openCARP/FACILE-RS)'
}

def create_parser(add_help=True):
    parser = cli.Parser(add_help=add_help)

    parser.add_argument('--grav-path', dest='GRAV_PATH', required=True,
                        help='Path to the grav repository directory.')
    parser.add_argument('--pipeline', dest='PIPELINE', required=True,
                        help='Name of the pipeline as specified in the GRAV metadata.')
    parser.add_argument('--log-level', dest='LOG_LEVEL', default='WARN',
                        help='Log level (ERROR, WARN, INFO, or DEBUG)')
    parser.add_argument('--log-file', dest='LOG_FILE',
                        help='Path to the log file')
    return parser


def main(args):
    # loop over the found pages and write the content into the files
    for page_path, page, source in collect_pages(args.GRAV_PATH, args.PIPELINE):
        page_metadata = page.metadata.get('experiments', {})
        experiments_metadata = []

        # add pages from zenodo communities
        for location in page_metadata.get('locations', []):
            location_response = fetch_dict(location)

            if list(location_response.keys()) == ['hits', 'aggregations', 'links']:
                for hit in location_response['hits']['hits']:
                    metadata = hit['metadata']

                    experiments_metadata.append({
                        'doi': metadata.get('doi'),
                        'title': metadata.get('title'),
                        'description': metadata.get('description'),
                        'publication_date': metadata.get('publication_date'),
                        'publication_year': datetime.date.fromisoformat(metadata.get('publication_date')).year,
                        'resource_type': metadata.get('resource_type')['title'],
                        'creators': metadata.get('creators'),
                        'keywords': metadata.get('keywords'),
                        'version': metadata.get('version'),
                        'license': metadata.get('license')['id'].upper()
                    })
            else:
                raise RuntimeError('This pipeline can only process input from the invenio API.')

        # add pages from individual dois by querying datacite
        for doi in page_metadata.get('dois', []):
            datacite_response = fetch_dict(DATACITE_URL + doi, DATACITE_HEADERS)
            datacite_metadata = datacite_response.get('data', {}).get('attributes')

            experiments_metadata.append({
                'doi': doi,
                'title': get_datacite_title(datacite_metadata),
                'description': get_datacite_description(datacite_metadata),
                'publication_date': get_datacite_publication_date(datacite_metadata),
                'publication_year': datacite_metadata.get('publicationYear'),
                'resource_type': datacite_metadata['types'].get('resourceType'),
                'creators': [
                    {
                        'name': creator['name'],
                        'orcid': get_datacite_orcid(creator),
                    } for creator in datacite_metadata.get('creators', [])
                ],
                'keywords': [subject['subject'] for subject in datacite_metadata.get('subjects')],
                'version': datacite_metadata.get('version'),
                'license': get_datacite_license(datacite_metadata)
            })

        # collect project metadata from the gitlab group
        gitlab_metadata = {}
        for group_url in page_metadata.get('gitlab_groups', []):
            group = fetch_dict(group_url)

            for project in group['projects']:
                project_doi = False
                project_metadata = {
                    'gitlab_url': project['web_url'],
                    'gitlab_branch': project['default_branch'],
                    'gitlab_path': 'run.py'
                }

                for topic in project['topics']:
                    match_doi = re.match(r'doi:(.+)', topic)
                    if match_doi:
                        project_doi = match_doi.groups(1)[0]
                    match_opencarp_version = re.match(r'opencarp-version:(.+)', topic)
                    if match_opencarp_version:
                        project_metadata['opencarp_version'] = match_opencarp_version.groups(1)[0]

                if project_doi:
                    gitlab_metadata[project_doi] = project_metadata

        for metadata in experiments_metadata:
            metadata['slug'] = slugify(metadata['title'])
            metadata['template'] = 'experiment'
            metadata['doi_url'] = f"https://doi.org/{metadata['doi']}"
            metadata.update(gitlab_metadata.get(metadata['doi'], {}))

            md_path = page_path.parent / metadata['slug'] / 'default.md'

            # create directories in the grav tree
            md_path.parent.mkdir(parents=True, exist_ok=True)

            try:
                catalog_page = frontmatter.load(md_path)
                catalog_page.content = metadata['description']
                catalog_page.metadata.update(metadata)
            except FileNotFoundError:
                catalog_page = frontmatter.Post(metadata['description'], **metadata)

            # write the grav file
            logger.debug('writing to %s', md_path)
            md_path.write_text(frontmatter.dumps(catalog_page))

        for file_path in page_path.parent.iterdir():
            if file_path.is_dir() and file_path.stem not in [metadata['slug'] for metadata in experiments_metadata]:
                shutil.rmtree(file_path)


def get_datacite_title(metadata):
    for title in metadata.get('titles', []):
        return title['title']


def get_datacite_description(metadata):
    for description in metadata.get('descriptions', []):
        if description['descriptionType'] == 'Abstract':
            return description['description']


def get_datacite_publication_date(metadata):
    for date_type in ['Issued', 'Created']:
        for date in metadata.get('dates', []):
            if date['dateType'] == date_type:
                return date['date']


def get_datacite_orcid(creator):
    for name_identifier in creator.get('nameIdentifiers', []):
        if name_identifier['nameIdentifierScheme'] == 'ORCID':
            return name_identifier['nameIdentifier']


def get_datacite_license(metadata):
    for rights in metadata.get('rightsList', []):
        return rights['rights']
