#
# bugreport module - object containing bug stuff for reporting
#   Written by Chris Lawrence <lawrencc@debian.org>
#   Copyright (C) 1999-2008 Chris Lawrence
#   Copyright (C) 2008-2023 Sandro Tosi <morph@debian.org>
#
# This program is freely distributable per the following license:
#
#  Permission to use, copy, modify, and distribute this software and its
#  documentation for any purpose and without fee is hereby granted,
#  provided that the above copyright notice appears in all copies and that
#  both that copyright notice and this permission notice appear in
#  supporting documentation.
#
#  I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
#  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
#  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
#  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
#  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
#  SOFTWARE.

from . import utils
from . import debbugs
import subprocess
import os

# to print errors
from .ui import text_ui as ui


class bugreport:
    "Encapsulates a bug report into a convenient object we can pass around."

    # Default character set for str(x)
    charset = 'utf-8'

    def __init__(self, package, subject='', body='', system='debian',
                 incfiles='', sysinfo=True,
                 followup=False, type='debbugs', mode=utils.MODE.STANDARD,
                 debsumsoutput=None, issource=False, **props):
        self.type = type
        for (k, v) in props.items():
            setattr(self, k, v)
        self.package = package
        self.subject = subject
        # try to convert followup to int (if it's not already), TypeError if
        # the conversion is impossible
        if followup and not isinstance(followup, int):
            try:
                self.followup = followup.bug_num
            except Exception:
                ui.long_message('Invalid value for followup, it must be a debianbts.Bugreport instance or an integer')
                raise TypeError
        else:
            self.followup = followup
        self.body = body
        self.mode = mode
        self.system = system
        self.incfiles = incfiles
        self.sysinfo = sysinfo
        self.debsumsoutput = debsumsoutput
        self.issource = issource

    def tset(self, value):
        if value not in ('debbugs', 'launchpad'):
            ui.long_message('invalid report type %s, defaulting to debbugs' %
                            value)
            self.__type = 'debbugs'
        else:
            self.__type = value

    def tget(self):
        return self.__type
    type = property(tget, tset)

    def __str__(self):
        un = os.uname()
        debinfo = ''
        shellpath = utils.realpath('/bin/sh')
        init = utils.get_init_system()
        lsminfo = utils.get_lsm_info()
        taint_flags = utils.get_kernel_taint_flags()

        locinfo = []
        langsetting = os.environ.get('LANG', 'C')
        allsetting = os.environ.get('LC_ALL', '')
        languagesetting = os.environ.get('LANGUAGE', '')
        for setting in ('LANG', 'LC_CTYPE', 'LANGUAGE'):
            if setting == 'LANG':
                env = langsetting
            elif setting == 'LANGUAGE':
                if languagesetting:
                    env = languagesetting
                else:
                    locinfo.append('LANGUAGE not set')
                    continue
            else:
                env = '%s (charmap=%s)' % (os.environ.get(setting, langsetting), subprocess.getoutput("locale charmap"))

                if allsetting and env:
                    env = "%s (ignored: LC_ALL set to %s)" % (env, allsetting)
                else:
                    env = allsetting or env
            locinfo.append('%s=%s' % (setting, env))

        locinfo = ', '.join(locinfo)

        ph = getattr(self, 'pseudoheaders', None)
        if ph:
            headers = '\n'.join(ph) + '\n'
        else:
            headers = ''

        version = getattr(self, 'version', None)
        if version:
            headers += 'Version: %s\n' % version

        body = getattr(self, 'body', '')

        # add NEWBIELINE only if it's less than advanced and the package is not
        # one of the specials (f.e. those with a dedicated function) also
        # thinking about those systems that don't have 'specials' dict
        # and if a body wasn't provided on the command line
        if self.mode < utils.MODE.ADVANCED and not body and self.package not in \
                list(debbugs.SYSTEMS[self.system].get('specials', {}).keys()):
            body = utils.NEWBIELINE + '\n\n' + body
        elif not body:
            body = '\n\n'
        else:
            body += '\n'

        if self.issource:
            reportto = 'Source'
        else:
            reportto = 'Package'

        if not self.followup:
            for (attr, name) in dict(severity='Severity',
                                     justification='Justification',
                                     tags='Tags',
                                     filename='File').items():
                a = getattr(self, attr, None)
                if a:
                    headers += '%s: %s\n' % (name, a)

            report = "%s: %s\n%s\n" % (reportto, self.package, headers)
        else:
            if hasattr(self, 'tags') and self.tags:
                headers += f'Control: tags -1 {self.tags}\n'
            report = "Followup-For: Bug #%d\n%s: %s\n%s\n" % (
                self.followup, reportto, self.package, headers)

        infofunc = debbugs.SYSTEMS[self.system].get('infofunc', debbugs.generic_infofunc)
        if infofunc:
            debinfo += infofunc()

        if un[0] == 'GNU':
            # Use uname -v on Hurd
            uname_string = un[3]
        else:
            kern = un[0]
            if kern.startswith('GNU/'):
                kern = kern[4:]

            uname_string = '%s %s' % (kern, un[2])
            if kern == 'Linux':
                kinfo = []

                if 'SMP' in un[3]:
                    threads = os.cpu_count()
                    if threads > 1:
                        kinfo += ['SMP w/%d CPU threads' % threads]
                    elif threads == 1:
                        kinfo += ['SMP w/1 CPU thread']
                else:
                    kinfo += ['UP']

                if 'PREEMPT' in un[3]:
                    kinfo += ['PREEMPT']

                if kinfo:
                    uname_string = '%s (%s)' % (uname_string, '; '.join(kinfo))

        if uname_string:
            debinfo += 'Kernel: %s\n' % uname_string
        if taint_flags:
            debinfo += 'Kernel taint flags: %s\n' % ', '.join(taint_flags)

        if locinfo:
            debinfo += 'Locale: %s\n' % locinfo
        if shellpath != '/bin/sh':
            debinfo += 'Shell: /bin/sh linked to %s\n' % shellpath
        if init:
            debinfo += 'Init: %s\n' % init
        if lsminfo:
            debinfo += 'LSM: %s\n' % lsminfo

        # Don't include system info for certain packages
        if self.sysinfo:
            report = "%s%s%s\n-- System Information:\n%s" % (report, body, self.incfiles, debinfo)
        else:
            report = "%s%s%s" % (report, body, self.incfiles)

        if hasattr(self, 'depinfo'):
            report += self.depinfo
        if hasattr(self, 'confinfo'):
            report += self.confinfo

        # add debsums output to the bug report
        if self.debsumsoutput:
            report += "\n-- debsums errors found:\n%s\n" % self.debsumsoutput

        return report

    def __repr__(self):
        params = ['%s=%s' % (k, self.k) for k in dir(self)]
        return 'bugreport(%s)' % ', '.join(params)
