import time
from datetime import datetime, timezone
from urllib.error import HTTPError
from lxml import etree as ET
import osc.core
from osclib.core import attribute_value_save

MAX_FROZEN_AGE = 6.5


class FreezeCommand(object):

    def __init__(self, api):
        self.api = api
        self.projectlinks = []

    def set_links(self):
        url = self.api.makeurl(['source', self.prj, '_meta'])
        f = self.api.retried_GET(url)
        root = ET.parse(f).getroot()
        links = root.findall('link')
        links.reverse()
        self.projectlinks = [link.get('project') for link in links]

    def set_bootstrap_copy(self):
        url = self.api.makeurl(['source', self.prj, '_meta'])

        f = self.api.retried_GET(url)
        oldmeta = ET.parse(f).getroot()

        meta = ET.fromstring(self.prj_meta_for_bootstrap_copy())
        meta.find('title').text = oldmeta.find('title').text
        meta.find('description').text = oldmeta.find('description').text
        for person in oldmeta.findall('person'):
            # the xml has a fixed structure
            meta.insert(2, ET.Element('person', role=person.get('role'), userid=person.get('userid')))

        self.api.retried_PUT(url, ET.tostring(meta))

    def create_bootstrap_aggregate(self):
        self.create_bootstrap_aggregate_meta()
        self.create_bootstrap_aggregate_file()

    def bootstrap_packages(self):
        url = self.api.makeurl(['build', f'{self.api.crings}:0-Bootstrap', '_result'])
        f = self.api.retried_GET(url)
        root = ET.parse(f).getroot().find('result')
        res = list()
        for e in root.findall('status'):
            name = e.get('package')
            if name in ['rpmlint-mini-AGGR']:
                continue
            res.append(name)
        return sorted(res)

    def create_bootstrap_aggregate_file(self):
        url = self.api.makeurl(['source', self.prj, 'bootstrap-copy', '_aggregate'])

        root = ET.Element('aggregatelist')
        a = ET.SubElement(root, 'aggregate',
                          {'project': f'{self.api.crings}:0-Bootstrap'})

        for package in self.bootstrap_packages():
            p = ET.SubElement(a, 'package')
            p.text = package

        ET.SubElement(a, 'repository', {'target': 'bootstrap_copy', 'source': 'standard'})
        ET.SubElement(a, 'repository', {'target': 'standard', 'source': 'nothing'})
        ET.SubElement(a, 'repository', {'target': 'images', 'source': 'nothing'})

        self.api.retried_PUT(url, ET.tostring(root))

    def create_bootstrap_aggregate_meta(self):
        url = self.api.makeurl(['source', self.prj, 'bootstrap-copy', '_meta'])

        root = ET.Element('package', {'project': self.prj, 'name': 'bootstrap-copy'})
        ET.SubElement(root, 'title')
        ET.SubElement(root, 'description')
        f = ET.SubElement(root, 'build')
        # this one is to toggle
        ET.SubElement(f, 'disable', {'repository': 'bootstrap_copy'})
        # this one is the global toggle
        ET.SubElement(f, 'disable')

        self.api.retried_PUT(url, ET.tostring(root))

    def build_switch_bootstrap_copy(self, state):
        url = self.api.makeurl(['source', self.prj, 'bootstrap-copy', '_meta'])
        pkgmeta = ET.parse(self.api.retried_GET(url)).getroot()

        for f in pkgmeta.find('build'):
            if f.get('repository', None) == 'bootstrap_copy':
                f.tag = state
        self.api.retried_PUT(url, ET.tostring(pkgmeta))

    def verify_bootstrap_copy_codes(self, codes):
        url = self.api.makeurl(['build', self.prj, '_result'], {'package': 'bootstrap-copy'})

        root = ET.parse(self.api.retried_GET(url)).getroot()
        for result in root.findall('result'):
            if result.get('repository') == 'bootstrap_copy':
                status = result.find('status')
                if status is None:
                    return False
                if not status.get('code') in codes:
                    return False
        return True

    def perform(self, prj, copy_bootstrap=True):
        self.prj = prj

        if self.api.is_adi_project(prj):
            src_prj = self.api.find_devel_project_from_adi_frozenlinks(self.prj)
            if src_prj is None:
                raise Exception(f"{self.prj} does not have a valid frozenlinks")
            else:
                self.api.update_adi_frozenlinks(self.prj, src_prj)
            return

        self.set_links()
        self.freeze_prjlinks()

        self.copy_weakremovers()

        build_status = self.api.get_flag_in_prj(prj, flag='build')

        # If there is not a bootstrap repository, there is not
        # anything more to do.
        if not self.is_bootstrap():
            return

        if copy_bootstrap:
            self.set_bootstrap_copy()
            self.create_bootstrap_aggregate()
            print("waiting for scheduler to disable...")
            while not self.verify_bootstrap_copy_codes(['disabled']):
                time.sleep(1)
            self.build_switch_bootstrap_copy('enable')
            print("waiting for scheduler to copy...")
            while not self.verify_bootstrap_copy_codes(['finished', 'succeeded']):
                time.sleep(1)
            self.build_switch_bootstrap_copy('disable')

        # Set the original build status for the project
        self.api.build_switch_prj(prj, build_status)
        # disable image build; after freeze, /standard needs to build.
        self.api.build_switch_prj(prj, 'disable', repository='images')

    def prj_meta_for_bootstrap_copy(self):
        root = ET.Element('project', {'name': self.prj})
        ET.SubElement(root, 'title')
        ET.SubElement(root, 'description')
        links = self.projectlinks or [f'{self.api.crings}:1-MinimalX']
        for lprj in links:
            ET.SubElement(root, 'link', {'project': lprj})

        # 'images' and 'product' repositories are closely related,
        # and they need to be handled the same way.
        image_repositories = ['images']
        if self.api.project_has_repo('product', self.prj):
            image_repositories.append('product')

        # build flag
        f = ET.SubElement(root, 'build')
        # this one is the global toggle
        ET.SubElement(f, 'disable')
        # this one stays
        ET.SubElement(f, 'disable', {'repository': 'bootstrap_copy'})
        # to be flipped by botmaster
        for repository in image_repositories:
            ET.SubElement(f, 'disable', {'repository': repository})

        # publish flag
        f = ET.SubElement(root, 'publish')
        ET.SubElement(f, 'disable')
        for repository in image_repositories:
            ET.SubElement(f, 'enable', {'repository': repository})

        # debuginfo flag
        f = ET.SubElement(root, 'debuginfo')
        ET.SubElement(f, 'enable')

        r = ET.SubElement(root, 'repository', {'name': 'bootstrap_copy'})
        ET.SubElement(r, 'path', {'project': self.api.cstaging, 'repository': 'standard'})
        for arch in self.api.cstaging_archs:
            a = ET.SubElement(r, 'arch')
            a.text = arch

        r = ET.SubElement(root, 'repository', {'name': 'standard', 'linkedbuild': 'all', 'rebuild': 'direct'})
        ET.SubElement(r, 'path', {'project': self.prj, 'repository': 'bootstrap_copy'})
        for arch in self.api.cstaging_archs:
            a = ET.SubElement(r, 'arch')
            a.text = arch

        for repository in image_repositories:
            r = ET.SubElement(root, 'repository', {'name': repository, 'linkedbuild': 'all'})
            ET.SubElement(r, 'path', {'project': self.prj, 'repository': 'standard'})

            if self.prj.startswith('SUSE:'):
                a = ET.SubElement(r, 'arch')
                a.text = 'local'

            a = ET.SubElement(r, 'arch')
            a.text = 'x86_64'

            if 'ppc64le' in self.api.cstaging_archs:
                a = ET.SubElement(r, 'arch')
                a.text = 'ppc64le'

        return ET.tostring(root)

    def freeze_prjlinks(self):
        sources = {}
        flink = ET.Element('frozenlinks')

        for lprj in self.projectlinks:
            fl = ET.SubElement(flink, 'frozenlink', {'project': lprj})
            sources = self.receive_sources(lprj, sources, fl)

        url = self.api.makeurl(['source', self.prj, '_project', '_frozenlinks'], {'meta': '1'})
        self.api.retried_PUT(url, ET.tostring(flink))
        attribute_value_save(self.api.apiurl, self.prj, 'FreezeTime', datetime.now(timezone.utc).isoformat())

    def receive_sources(self, prj, sources, flink):
        url = self.api.makeurl(['source', prj], {'view': 'info', 'nofilename': '1'})
        f = self.api.retried_GET(url)
        root = ET.parse(f).getroot()

        for si in root.findall('sourceinfo'):
            package = self.check_one_source(flink, si)
            sources[package] = 1
        return sources

    def check_one_source(self, flink, si):
        package = si.get('package')

        # If the package is an internal one (e.g _product)
        if package.startswith('_'):
            return None

        # Ignore packages with an origing (i.e. with an origin
        # different from the current project)
        if si.find('originproject') is not None:
            return None

        if package in ['rpmlint-mini-AGGR']:
            return package  # we should not freeze aggregates
        ET.SubElement(flink, 'package', {'name': package, 'srcmd5': si.get('srcmd5'), 'vrev': si.get('vrev')})
        return package

    def copy_weakremovers(self):
        try:
            targeturl = self.api.makeurl(['source', self.prj, '000release-packages', 'weakremovers.inc'],
                                         {'comment': 'Update weakremovers.inc'})
            oldinc = osc.core.http_GET(targeturl).read()
            targeturl_exists = True
        except HTTPError:
            targeturl_exists = False
        try:
            sourceurl = self.api.makeurl(['source', self.api.project, '000release-packages', 'weakremovers.inc'])
            inc = osc.core.http_GET(sourceurl).read()
            sourceurl_exists = True
        except HTTPError:
            sourceurl_exists = False
        if targeturl_exists != sourceurl_exists:
            raise Exception("weakremover.inc doesn't exist in both Staging and Parent project, please fix")
        if not (targeturl_exists) and not (sourceurl_exists):
            # nothing to do
            return
        if inc != oldinc:
            osc.core.http_PUT(targeturl, data=inc)

    def is_bootstrap(self):
        """Check if there is a bootstrap copy repository."""
        url = self.api.makeurl(['source', self.prj, '_meta'])
        root = ET.parse(self.api.retried_GET(url)).getroot()

        for repo in root.findall('.//repository'):
            if 'bootstrap_copy' == repo.get('name'):
                return True
        return False
