#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020 Marcel Bollmann <marcel@bollmann.me>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Usage: verify_filelist.py FILELIST [options] [--help]

Compares external files expected by the Anthology to a list of filenames and
their checksums (generated by crc32).

Arguments:
  FILELIST                 Output of `crc32`

Options:
  -r, --list-remaining     Also output files in FILELIST that were not expected.
  --importdir=DIR          Directory to import XML files from. [default: {scriptdir}/../data]
  --debug                  Output debug-level log messages.
  -h, --help               Display this helpful text.
"""

from collections import defaultdict
from docopt import docopt
from glob import glob
from lxml import etree
import logging as log
import os
from urllib.parse import urlparse

from anthology.utils import SeverityTracker


def get_expected_path(filetype, filename, collection_id):
    """Maps the relative URL to the expected path on the server.

    THIS IS HIGHLY DEPENDENT ON THE SERVER CONFIGURATION AND .HTACCESS!
    """
    if collection_id[0].isdigit():  # new-style ID
        folder = collection_id.split(".")[-1]
        filename = os.path.join(folder, filename)
    else:
        filename = os.path.join(collection_id[0], collection_id, filename)
    filename = os.path.join(filetype, filename)
    return filename


def is_remote_url(url):
    return bool(urlparse(url).netloc)


def read_checksums(filename):
    checksums = {}
    with open(filename, "r") as f:
        for line in f:
            if not line.strip():
                continue
            crc = line[:8]
            path = line[8:].strip()
            checksums[path] = crc
    return checksums


def main(datadir, crcfile, opts):
    checksums = read_checksums(crcfile)
    problems = defaultdict(int)

    for xml_file in list(glob(f"{datadir}/xml/*.xml")):
        tree = etree.parse(xml_file)
        root = tree.getroot()
        collection_id = root.get("id")

        for element in root.iter("url", "attachment", "revision", "erratum"):
            if element.getparent().tag not in ("paper", "frontmatter", "meta"):
                continue
            if element.tag in ("url", "erratum"):
                path = f"{element.text}.pdf"
                filetype = "pdf"
            elif element.tag == "revision":
                path = f"{element.get('href')}.pdf"
                filetype = "pdf"
            elif element.tag == "attachment":
                path = element.text
                filetype = "attachments"

            if is_remote_url(path):
                continue
            path = get_expected_path(filetype, path, collection_id)
            expected_checksum = element.get("hash")
            if path not in checksums:
                print("\t".join(("missing", filetype, path)))
                problems[filetype] += 1
            elif checksums[path] != expected_checksum:
                print(
                    "\t".join(
                        (
                            "crc-mismatch",
                            filetype,
                            path,
                            expected_checksum,
                            checksums[path],
                        )
                    )
                )
                problems["checksum"] += 1
            else:
                del checksums[path]

    if not problems:
        log.info("No problems detected.")
    if problems["pdf"]:
        log.error(f"{len(problems['pdf']):6d} PDF files missing in list.")
    if problems["attachment"]:
        log.error(f"{len(problems['attachment']):6d} attachments missing in list.")
    if problems["checksum"]:
        log.error(f"{len(problems['checksum']):6d} checksums don't match.")

    if checksums:
        log.warning(f"{len(checksums):6d} files remaining in list that were not used.")
        if opts["list_remaining"]:
            for path in checksums:
                print("\t".join(("unexpected", "", path)))


if __name__ == "__main__":
    args = docopt(__doc__)
    scriptdir = os.path.dirname(os.path.abspath(__file__))
    if "{scriptdir}" in args["--importdir"]:
        args["--importdir"] = os.path.abspath(
            args["--importdir"].format(scriptdir=scriptdir)
        )

    log_level = log.DEBUG if args["--debug"] else log.INFO
    log.basicConfig(format="%(levelname)-8s %(message)s", level=log_level)
    tracker = SeverityTracker()
    log.getLogger().addHandler(tracker)

    opts = dict(
        list_remaining=bool(args["--list-remaining"]),
    )
    main(args["--importdir"], args["FILELIST"], opts)

    if tracker.highest >= log.ERROR:
        exit(1)
