# SPDX-FileCopyrightText: 2023-2026 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

from __future__ import annotations

import subprocess
import time
from pathlib import Path
from typing import TYPE_CHECKING

import pytest
from playwright.sync_api import Browser, BrowserContext, BrowserType, Page, expect

from univention.config_registry import handler_set, handler_unset
from univention.testing import udm as _udm
from univention.testing.browser import logger
from univention.testing.browser.generic_udm_module import UserModule
from univention.testing.browser.ldap_directory import LDAPDirectory
from univention.testing.browser.lib import UMCBrowserTest
from univention.testing.browser.selfservice import SelfService
from univention.testing.browser.sidemenu import SideMenuLicense, SideMenuUser
from univention.testing.browser.suggestion import AppCenterCacheTest
from univention.testing.browser.univentionconfigurationregistry import UniventionConfigurationRegistry

from . import check_for_backtrace, save_screenshot, save_trace


if TYPE_CHECKING:
    from collections.abc import Generator, Iterator


@pytest.fixture(scope='session', autouse=True)
def suppress_notifications():
    handler_set(['umc/web/hooks/suppress_umc_notifications=suppress_umc_notifications'])
    yield
    handler_unset(['umc/web/hooks/suppress_umc_notifications'])


phase_report_key = pytest.StashKey[dict[str, pytest.CollectReport]]()


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()

    item.stash.setdefault(phase_report_key, {})[rep.when] = rep


@pytest.fixture(scope='session')
def ucs_browser_context_args(browser_context_args):
    return {
        **browser_context_args,
        'ignore_https_errors': True,
    }


@pytest.fixture(scope='session')
def ucs_browser_type_launch_args(browser_type_launch_args):
    return {
        **browser_type_launch_args,
        'executable_path': '/usr/bin/chromium',
        'args': [
            '--disable-gpu',
        ],
    }


@pytest.fixture(scope='session')
def browser_context_args(browser_context_args):
    return {
        **browser_context_args,
        'ignore_https_errors': True,
    }


@pytest.fixture(scope='session')
def browser_type_launch_args(browser_type_launch_args):
    return {
        **browser_type_launch_args,
        'executable_path': '/usr/bin/chromium',
        'args': [
            '--disable-gpu',
        ],
    }


@pytest.fixture(scope='module')
def udm_module_scope() -> Iterator[_udm.UCSTestUDM]:
    """Auto-reverting UDM wrapper."""
    with _udm.UCSTestUDM() as udm:
        yield udm


@pytest.fixture
def ucr_module(umc_browser_test: UMCBrowserTest):
    return UniventionConfigurationRegistry(umc_browser_test)


@pytest.fixture
def user_module(umc_browser_test: UMCBrowserTest):
    return UserModule(umc_browser_test)


@pytest.fixture
def side_menu_license(umc_browser_test: UMCBrowserTest):
    return SideMenuLicense(umc_browser_test)


@pytest.fixture
def side_menu_user(umc_browser_test: UMCBrowserTest):
    return SideMenuUser(umc_browser_test)


@pytest.fixture
def self_service(umc_browser_test: UMCBrowserTest) -> SelfService:
    return SelfService(umc_browser_test)


@pytest.fixture
def ldap_directory(umc_browser_test: UMCBrowserTest) -> LDAPDirectory:
    return LDAPDirectory(umc_browser_test)


def kill_univention_management_console_module():
    try:
        subprocess.run(
            ['pkill', '-f', '/usr/sbin/univention-management-console-module'],
            check=True,
        )
    except subprocess.CalledProcessError as e:
        if e.returncode != 1:
            logger.exception('failed killing module processes')
            raise


@pytest.fixture(scope='module')
def kill_module_processes_module():
    logger.info('killing module processes')
    kill_univention_management_console_module()


@pytest.fixture
def kill_module_processes():
    logger.info('killing module processes')
    kill_univention_management_console_module()


def setup_browser_context(context, start_tracing=True):
    context.set_default_timeout(30 * 1000)
    expect.set_options(timeout=30 * 1000)
    if start_tracing:
        context.tracing.start(screenshots=True, snapshots=True, sources=True)
    page = context.new_page()
    return page


@pytest.fixture(scope='module')
def context_module_scope(
    browser_type: BrowserType,
    ucs_browser_type_launch_args: dict,
    ucs_browser_context_args: dict,
):
    browser = browser_type.launch(**ucs_browser_type_launch_args)
    return browser.new_context(**ucs_browser_context_args)


@pytest.fixture(scope='module')
def umc_browser_test_module(
    context_module_scope: BrowserContext,
    kill_module_processes_module,
) -> UMCBrowserTest:
    page = setup_browser_context(context_module_scope)
    tester = UMCBrowserTest(page)

    return tester


@pytest.fixture
def umc_browser_test(
    browser_type: BrowserType,
    ucs_browser_type_launch_args: dict,
    ucs_browser_context_args: dict,
    request: pytest.FixtureRequest,
    kill_module_processes,
    ucr,
) -> Generator[UMCBrowserTest, None, None]:
    browser = browser_type.launch(**ucs_browser_type_launch_args)
    context = browser.new_context(**ucs_browser_context_args)
    page = setup_browser_context(context)
    yield UMCBrowserTest(page)

    teardown_umc_browser_test(request, ucr, page, context, browser)


def teardown_umc_browser_test(
    request: pytest.FixtureRequest,
    ucr,
    page: Page | list[Page],
    context: BrowserContext,
    browser: Browser,
):
    try:
        report = request.node.stash[phase_report_key]
    except KeyError:
        logger.warning(
            'phase_report_key has not been found in node stash. Skipping trace saving and backtrace checking.',
        )
        return
    if not isinstance(page, list):
        page = [page]
    try:
        if 'call' in report and report['call'].failed:
            failure_timestamp = time.time_ns()

            save_trace(context, request.node.name, Path('browser').resolve(), ucr, timestamp=failure_timestamp)
            for i, p in enumerate(page):
                save_screenshot(p, request.node.name, Path('browser'), ucr, i, timestamp=failure_timestamp)
                check_for_backtrace(p, page_index=i)
        else:
            context.tracing.stop()
    finally:
        context.close()
        # close browser instance if requested,
        # useful for parameterized test
        markers = list(request.node.iter_markers('close_browser'))
        if len(markers) == 1:
            browser.close()


@pytest.fixture
def app_center_cache():
    app_center_cache = AppCenterCacheTest()
    yield app_center_cache
    app_center_cache.restore()
