#!/usr/bin/env python
"""
prov-compare -- Compare two PROV-JSON, PROV-XML, or RDF (PROV-O) files for equivalence

@author:     Trung Dong Huynh

@copyright:  2025 Trung Dong Huynh

@license:    MIT Licence

@contact:    trungdong@donggiang.com
@deffield    updated: 2025-06-07
"""

from __future__ import annotations

import logging
import os
import sys
import traceback
from argparse import ArgumentParser, FileType, RawDescriptionHelpFormatter

from prov.model import ProvDocument

logger = logging.getLogger(__name__)

__all__: list[str] = []
__version__ = 0.1
__date__ = "2015-06-16"
__updated__ = "2025-06-07"

DEBUG = 0
TESTRUN = 0
PROFILE = 0


class CLIError(Exception):
    """Generic exception to raise and log different fatal errors."""

    def __init__(self, msg: str):
        super().__init__(type(self))
        self.msg = f"E: {msg}"

    def __str__(self) -> str:
        return self.msg


def main(argv: list[str] | None = None) -> int:  # IGNORE:C0111
    """Run the ``prov-compare`` command-line tool.

    Parses two positional file arguments plus ``-f/--format1`` and
    ``-F/--format2`` (each defaulting to ``"json"``), deserializes both
    files, and compares the resulting documents for equality.

    Args:
        argv: Extra command-line arguments. If not ``None``, they are
            appended to ``sys.argv`` (which is *not* replaced) before
            argument parsing, so ``sys.argv[0]`` is still used as the
            program name.

    Returns:
        ``0`` if the two documents are equal, ``1`` if they differ (the
        truthiness of ``doc1 != doc2``), or ``2`` if an exception was raised
        while parsing arguments or deserializing a file (unless
        ``DEBUG``/``TESTRUN`` is set, in which case the exception propagates
        instead). Unlike ``prov-convert``, ``KeyboardInterrupt`` is not
        caught here and will propagate.
    """

    if argv is None:
        argv = sys.argv
    else:
        sys.argv.extend(argv)

    program_name = os.path.basename(sys.argv[0])
    program_version = f"v{__version__}"
    program_build_date = str(__updated__)
    program_version_message = f"%(prog)s {program_version} ({program_build_date})"
    program_shortdesc = __doc__.split("\n")[1]
    program_license = f"""{program_shortdesc}

  Copyright 2025 Trung Dong Huynh.

  Licensed under the MIT License
  https://github.com/trungdong/prov/blob/master/LICENSE

  Distributed on an "AS IS" basis without warranties
  or conditions of any kind, either express or implied.

USAGE
"""

    try:
        # Setup argument parser
        parser = ArgumentParser(
            description=program_license, formatter_class=RawDescriptionHelpFormatter
        )
        parser.add_argument("file1", nargs="?", type=FileType("r"))
        parser.add_argument("file2", nargs="?", type=FileType("r"))
        parser.add_argument(
            "-f",
            "--format1",
            dest="format1",
            action="store",
            default="json",
            help="File 1's format: json or xml",
        )
        parser.add_argument(
            "-F",
            "--format2",
            dest="format2",
            action="store",
            default="json",
            help="File 2's format: json or xml",
        )
        parser.add_argument(
            "-V", "--version", action="version", version=program_version_message
        )

        args = None
        try:
            # Process arguments
            args = parser.parse_args()
            doc1 = ProvDocument.deserialize(args.file1, format=args.format1.lower())
            doc2 = ProvDocument.deserialize(args.file2, format=args.format2.lower())
            return doc1 != doc2

        finally:
            if args:
                if args.file1:
                    args.file1.close()
                if args.file2:
                    args.file2.close()

    except Exception as e:
        if DEBUG or TESTRUN:
            traceback.print_exc()
            raise e
        indent = len(program_name) * " "
        sys.stderr.write(program_name + ": " + str(e) + "\n")
        sys.stderr.write(indent + "  for help use --help")
        return 2


if __name__ == "__main__":
    logging.basicConfig(level=(logging.DEBUG if DEBUG else logging.INFO))
    if TESTRUN:
        import doctest

        doctest.testmod()
    if PROFILE:
        import cProfile
        import pstats

        profile_filename = "prov_compare_profile.txt"
        cProfile.run("main()", profile_filename)
        with open("profile_stats.txt", "wb") as statsfile:
            p = pstats.Stats(profile_filename, stream=statsfile)
            stats = p.strip_dirs().sort_stats("cumulative")
            stats.print_stats()
        sys.exit(0)
    sys.exit(main())
