#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright © 2018 Endless Mobile, Inc.
# Copyright © 2025 Canonical Ltd.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA  02110-1301  USA

"""Integration tests for GLib utilities."""

import os
import shutil
import subprocess
import tempfile
import unittest
import sys

from dataclasses import dataclass
from enum import Enum


class ProgramType(Enum):
    """Enum to define the kind of tool to use"""

    NATIVE = 1
    INTERPRETED = 2


@dataclass
class Result:
    """Class for keeping track of the executable result."""

    info: subprocess.CompletedProcess
    out: str
    err: str
    subs: dict


class TestProgramRunner(unittest.TestCase):
    """Integration test for running glib-based tools.

    This can be run when installed or uninstalled. When uninstalled, it
    requires G_TEST_BUILDDIR or _G_TEST_PROGRAM_RUNNER_PATH to be set.
    """

    PROGRAM_NAME: str = None
    PROGRAM_TYPE: ProgramType = ProgramType.NATIVE
    INTERPRETER: str = None

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.assertTrue(cls.PROGRAM_NAME, "class PROGRAM_NAME must be set")

        ext = ""
        if cls.PROGRAM_TYPE == ProgramType.NATIVE and os.name == "nt":
            ext = ".exe"

        cls._program_name = f"{cls.PROGRAM_NAME}{ext}"

        if "_G_TEST_PROGRAM_RUNNER_PATH" in os.environ:
            cls.__program = os.path.join(
                os.environ["_G_TEST_PROGRAM_RUNNER_PATH"], cls._program_name
            )
        elif "G_TEST_BUILDDIR" in os.environ:
            cls.__program = os.path.join(
                os.environ["G_TEST_BUILDDIR"], cls._program_name
            )
        else:
            cls.__program = os.path.join(os.path.dirname(__file__), cls._program_name)
            if not os.path.exists(cls.__program):
                cls.__program = shutil.which(cls._program_name)

    def setUp(self):
        print(f"{self.PROGRAM_NAME}: {self.__program}")
        self.assertTrue(os.path.exists(self.__program))

        self.tmpdir = tempfile.TemporaryDirectory()
        self.addCleanup(self.tmpdir.cleanup)
        old_cwd = os.getcwd()
        self.addCleanup(os.chdir, old_cwd)
        os.chdir(self.tmpdir.name)
        print("tmpdir:", self.tmpdir.name)

    def runTestProgram(
        self,
        *args,
        should_fail=False,
        timeout_seconds=10,
        wrapper_args=[],
        environment={},
    ) -> Result:
        argv = [self.__program]

        argv.extend(*args)

        # shebang lines are not supported on native
        # Windows consoles
        if self.PROGRAM_TYPE == ProgramType.INTERPRETED and os.name == "nt":
            argv.insert(0, self.INTERPRETER if self.INTERPRETER else sys.executable)

        argv = wrapper_args + argv

        env = os.environ.copy()
        env["LC_ALL"] = "C.UTF-8"
        env["G_DEBUG"] = "fatal-warnings"
        env.update(environment)

        print("Running:", argv)

        # We want to ensure consistent line endings...
        info = subprocess.run(
            argv,
            timeout=timeout_seconds,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            env=env,
            universal_newlines=True,
            text=True,
            encoding="utf-8",
            check=False,
        )

        result = Result(
            info=info,
            out=info.stdout.strip(),
            err=info.stderr.strip(),
            subs=self._getSubs(),
        )

        print("Return code:", result.info.returncode)
        print("Output:\n", result.out)
        print("Error:\n", result.err)

        if should_fail:
            with self.assertRaises(subprocess.CalledProcessError):
                info.check_returncode()
        else:
            info.check_returncode()

        return result

    def _getSubs(self) -> dict:
        return {}