#!/usr/bin/python3
#
# yelp-build-url-handler
# Copyright (C) 2023, 2024 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os
import sys
import subprocess
import urllib.parse
import gi
import errno
import dbus
from dbus import Interface, SessionBus

gi.require_version('Gio', '2.0')
from gi.repository import Gio, GLib

class YelpBuildUrlHandler:
    def main(self):
        if len(sys.argv) != 2:
            print('Usage: yelp-build-url-handler <URL>', file=sys.stderr)
            return 1
        url = sys.argv[1]
        return self.handle_url(url)

    def determine_paths_from_url(self, url):
        parsed_url = urllib.parse.urlparse(url)
        if parsed_url.scheme != 'help':
            print(f'Invalid URI scheme: {parsed_url.scheme}', file=sys.stderr)
            return None

        path = parsed_url.path.lstrip('/')
        languages = GLib.get_language_names() + ['C']

        for language in languages:
            base_path = os.path.join('/usr/share/help', language)
            cache_path = os.path.join(GLib.get_user_cache_dir(), 'yelp-build', language)
            full_source_path = os.path.join(base_path, path)

            if os.path.isdir(full_source_path):
                source_dir = full_source_path
                output_dir = os.path.join(cache_path, path)
                output_file = os.path.join(output_dir, 'index.html')
                return {
                    'source_dir': source_dir,
                    'output_dir': output_dir,
                    'output_file': output_file,
                    'full_source_path': full_source_path
                }
            else:
                full_source_file = full_source_path + '.page'
                if os.path.isfile(full_source_file):
                    source_dir = os.path.dirname(full_source_file)
                    output_file = os.path.join(cache_path, path + '.html')
                    output_dir = os.path.dirname(output_file)
                    return {
                        'source_dir': source_dir,
                        'output_dir': output_dir,
                        'output_file': output_file,
                        'full_source_path': full_source_file
                    }

        print(f'Help content not found for path: {path}', file=sys.stderr)
        return None

    def handle_url(self, url):
        paths = self.determine_paths_from_url(url)

        if paths is None:
            return 1

        source_dir = paths['source_dir']
        output_dir = paths['output_dir']
        output_file = paths['output_file']
        full_source_path = paths['full_source_path']

        cache_stale = True
        try:
            output_status = os.stat(output_dir)
            source_status = os.stat(source_dir)
            if output_status.st_mtime < source_status.st_mtime:
                cache_stale = False
        except OSError as e:
            pass

        if cache_stale:
            cache_stale = not self.generate_html(source_dir, output_dir)

            if cache_stale:
                print(f'Failed to generate HTML for path {full_source_path}', file=sys.stderr)
                return 1

        sandboxed_dir = self.grant_access_to_browser(output_dir)
        if sandboxed_dir is None:
            print(f'Failed to grant browser access to generated HTML for path {full_source_path}', file=sys.stderr)
            return 1

        basename = os.path.basename(output_file)
        sandboxed_file = os.path.join(sandboxed_dir, basename)
        redirect_file = self.write_redirect_file(sandboxed_dir, sandboxed_file)
        if redirect_file is None:
            return 1

        if not self.show_generated_html(redirect_file):
            print('Failed to start browser', file=sys.stderr)
            return 1

    def generate_html(self, source_dir, output_dir):
        os.makedirs(output_dir, exist_ok=True)
        argv = ['yelp-build', 'html', '.', '-o', output_dir]
        try:
            subprocess.run(argv, cwd=source_dir, check=True)
            return True
        except subprocess.CalledProcessError as e:
            print(f'Could not run "{" ".join(argv)}": {e}', file=sys.stderr)
            return False

    def grant_access_to_browser(self, output_dir):
        default_browser = Gio.AppInfo.get_default_for_type('text/html', False)
        if default_browser is None:
            print('Default web browser not found!', file=sys.stderr)
            return None

        app_id = default_browser.get_id()
        if app_id is None:
            print('Default web browser has no app ID!', file=sys.stderr)
            return None

        app_id = app_id.removesuffix('.desktop')

        try:
            dir_fd = os.open(output_dir, os.O_PATH)
        except OSError as e:
            print(f'Failed to open directory {output_dir}: {e}', file=sys.stderr)
            return None

        bus = SessionBus()
        try:
            portal_documents_object = bus.get_object('org.freedesktop.portal.Documents', '/org/freedesktop/portal/documents')
            portal_documents = Interface(portal_documents_object, dbus_interface='org.freedesktop.portal.Documents')
        except dbus.DBusException as e:
            print(f'Failed to connect to Documents portal: {e}', file=sys.stderr)
            os.close(dir_fd)
            return None

        flags = (
            1 << 0    # ReuseExisting
            | 1 << 1  # Persistent
            | 1 << 3  # ExportDirectory
        )
        permissions = ['read']

        try:
            doc_with_signature, *_ = portal_documents.AddFull([dbus.types.UnixFd(dir_fd)], flags, app_id, permissions)
            if not doc_with_signature:
                print('No document IDs returned from portal', file=sys.stderr)
                os.close(dir_fd)
                return None

            doc_id, *_ = doc_with_signature
            sandboxed_dir = os.path.join(GLib.get_user_runtime_dir(), 'doc', doc_id, os.path.basename(output_dir))
            os.close(dir_fd)
        except dbus.DBusException as e:
            print(f'Failed to call AddFull method: {e}', file=sys.stderr)
            os.close(dir_fd)
            return None

        return sandboxed_dir

    def show_generated_html(self, output):
        try:
            uri = GLib.filename_to_uri(output)
            Gio.AppInfo.launch_default_for_uri(uri, None)
        except Exception as e:
            print(f'Failed to launch default application: {e}', file=sys.stderr)
            return False

        return True

    def write_redirect_file(self, sandboxed_dir, sandboxed_file):
        html = f'<meta http-equiv="refresh" content="0; url=file://{sandboxed_file}">'
        redirect_file = os.path.join(sandboxed_dir, 'yelp-build-redirect.html')

        try:
            with open(redirect_file, 'w') as f:
                f.write(html)
        except Exception as e:
            print(f'Error writing redirect file {redirect_file}: {e}', file=sys.stderr)
            return None

        return redirect_file

if __name__ == '__main__':
    try:
        sys.exit(YelpBuildUrlHandler().main())
    except KeyboardInterrupt:
        sys.exit(1)

