#!/usr/bin/python3
# pylint: disable=invalid-name

"""
A JSON-based interface for depsolving using DNF.

Reads a request through stdin and prints the result to stdout.
In case of error, a structured error is printed to stdout as well.
"""
import json
import os
import sys
import tempfile

from osbuild.solver.exceptions import (
    GPGKeyReadError,
    MarkingError,
    DepsolveError,
    NoReposError,
    RepoError,
    InvalidAPIVersionError,
    InvalidRequestError,
)
from osbuild.solver import api
from osbuild.solver.request import SolverCommand
from typing import Dict, Any

# Load the solver configuration
config = {"use_dnf5": False}
config_path = os.environ.get("OSBUILD_SOLVER_CONFIG") or "/usr/lib/osbuild/solver.json"
try:
    with open(config_path, encoding="utf-8") as f:
        loaded_config = json.load(f)
        config.update(loaded_config)
except FileNotFoundError:
    pass

if config.get("use_dnf5", False):
    from osbuild.solver.dnf5 import DNF5 as Solver
else:
    from osbuild.solver.dnf import DNF as Solver


def solve(request_dict: Dict[str, Any]):
    with tempfile.TemporaryDirectory() as persistdir:
        try:
            request = api.parse_request(request_dict)
            solver = Solver(request, persistdir, config.get("license_index_path"))
            if request.command == SolverCommand.DUMP:
                return solver.dump(), None
            if request.command == SolverCommand.DEPSOLVE:
                return solver.depsolve(request.depsolve_args), None
            if request.command == SolverCommand.SEARCH:
                return solver.search(request.search_args), None
            raise InvalidRequestError(f"Invalid command '{request.command}'")
        except GPGKeyReadError as e:
            printe("error reading gpgkey")
            return None, {
                "kind": type(e).__name__,
                "reason": str(e)
            }
        except RepoError as e:
            return None, {
                "kind": "RepoError",
                "reason": f"There was a problem reading a repository: {e}"
            }
        except NoReposError as e:
            return None, {
                "kind": "NoReposError",
                "reason": f"There was a problem finding repositories: {e}"
            }
        except MarkingError as e:
            printe("error install_specs")
            return None, {
                "kind": "MarkingErrors",
                "reason": f"Error occurred when marking packages for installation: {e}"
            }
        except DepsolveError as e:
            printe("error depsolve")
            # collect list of packages for error
            pkgs = []
            # NB: we assume that the request was valid and depsolving failed
            for t in request.depsolve_args.transactions:
                pkgs.extend(t.package_specs)
            return None, {
                "kind": "DepsolveError",
                "reason": f"There was a problem depsolving {', '.join(pkgs)}: {e}"
            }
        except (InvalidRequestError, InvalidAPIVersionError) as e:
            printe("error invalid request")
            return None, {
                "kind": "InvalidRequest",
                "reason": str(e)
            }
        except Exception as e:  # pylint: disable=broad-exception-caught
            printe("error traceback")
            import traceback
            return None, {
                "kind": type(e).__name__,
                "reason": str(e),
                "traceback": traceback.format_exc()
            }


def printe(*msg):
    print(*msg, file=sys.stderr)


def fail(err):
    printe(f"{err['kind']}: {err['reason']}")
    if 'traceback' in err:
        printe(err['traceback'])
    print(json.dumps(err))
    sys.exit(1)


def respond(result):
    print(json.dumps(result))


def main():
    request_dict = json.load(sys.stdin)
    result, err = solve(request_dict)
    if err:
        fail(err)
    else:
        respond(result)


if __name__ == "__main__":
    main()
