import os
import re
import sys
import base64
import json
import requests
import urlparse
import ast
from time import sleep
from datetime import datetime
from datetime import timedelta

import urllib2
import copy

from osc import conf, core
from common.buildservice import BuildService
from common.buildtrigger import trigger_info, trigger_next, remote_jenkins_build_job
from gitbuildsys.errors import ObsError
import xml.etree.ElementTree as ET
import xml.etree.cElementTree as ElementTree
from common.mapping import get_ref_map, get_sync_map_list
from common.gerrit import GerritEnv
from common.upload_service import UploadError

class LocalError(Exception):
    """Local error exception."""
    pass

class CreateProjectObs(object):
    """ create project to obs
        PreRelease Project fotmat is :
                home:prerelease:<origin_target_obs_project>:submit:basechecker """

    def post_project_create(self, trigger_info, project_name):
        content = trigger_info

        bm_start_datetime = datetime.now()
        # get vars
        bm_stage = "Sync_SR_Submit_BaseCheck"
        bm_sync_git_tag = content.get('virtual_sr')
        bm_end_datetime = datetime.now()
        triggered_by = content.get('submitter')
        project_to_create = project_name
        bm_data = {"bm_stage": bm_stage,
                   "commit_date": str(bm_end_datetime),
                   "commit_msg": "N/A",
                   "submit_date": str(bm_end_datetime),
                   "submit_msg": "N/A",
                   "submitter": "<%s>" % triggered_by,
                   #                   "bm_member_sr_info" : {project_to_create: requests_item[project_to_create][r:(r+split_step)]},
                   "bm_sync_git_tag": bm_sync_git_tag,
                   "gerrit_project": "N/A",
                   "gerrit_newrev": "N/A",
                   "gerrit_account_name": "<%s>" % triggered_by,
                   "bm_start_datetime": str(bm_start_datetime),
                   "bm_end_datetime": str(bm_end_datetime),
                   "bm_src_project_lst": [project_to_create],
                   "git_tag_list": content.get('sr_list')
                   }
        trigger_next("BUILD-MONITOR", bm_data)

    def change_project_config(self, build, base_project, profile_project, git_tag):
        """
        Change release name from project config in OBS
        Add the datetime into release name.
        Eg: 'Release: <CI_CNT>.<B_CNT>' ----> 'Release: 20141010.<CI_CNT>.<B_CNT>'
        """
        # get project config
        base_config = build.get_project_config(base_project)
        profile_config = build.get_project_config(profile_project)
        config = base_config + '%s\n' % profile_config

        release_name = 'Release: %s' % (git_tag.split(':')[-1])
        res = re.findall(r'^Release: ?\S+$', config, flags=re.MULTILINE)
        if res:
            if git_tag.split(':')[-1] not in res[0]:
                note = '#Insert time from submission into release name\n'
                release_name = '%s.%s' % (release_name,
                                          res[0].split('Release:')[1].strip())
                config = config.replace(
                    res[0], '%s%s' % (note, release_name), 1)
        else:
            note = '#Add release name into prjconf\n'
            config = note + '%s\n' % release_name + config

        # Add rpmbuild stage option
        if os.getenv('PRERELEASE_RPMBUILD_STAGE'):
            # Check if we've got required fields in TRIGGER_INFO
            if not os.getenv('PRERELEASE_RPMBUILD_STAGE') in ('ba', 'bb'):
                print 'Error: PRERELEASE_RPMBUILD_STAGE %s' % (os.getenv('PRERELEASE_RPMBUILD_STAGE'))
            else:
                rpmbuildstage = 'Rpmbuildstage: -%s' % (os.getenv('PRERELEASE_RPMBUILD_STAGE'))
                res = re.findall(r'^Rpmbuildstage: ?\S+$', config, flags=re.MULTILINE)
                if res:
                    config = config.replace(res[0], '%s' % (rpmbuildstage), 1)
                else:
                    config = config + '#Add RpmbuildStage option into prjconf\n' + \
                        '%s\n' % (rpmbuildstage)

        # Add "CopyLinkedPackages: yes" for prerelease projects.
        if not re.search("CopyLinkedPackages:", config):
            config = config + "\nCopyLinkedPackages: yes\n"

        return config

    def run_create_staging_base_project_obs(self, obs_api, obs_user, obs_passwd, remote_obs_api, remote_obs_user, remote_obs_passwd):
        print(
            "-------------------------[JOB STARTED : RUN CREATE STAGING BASE]------------------------------------")

        remote_build = BuildService(
            remote_obs_api, remote_obs_user, remote_obs_passwd)
        build = BuildService(obs_api, obs_user, obs_passwd)

        content = trigger_info(os.getenv('TRIGGER_INFO'))
        project_type = "home:prerelease"
        ref_name = "%s:%s" % (project_type, content.get("obs_target_prj"))
        tag_name = content.get("virtual_sr").replace("/", ":")
        base_ref_project = content.get('ref_base_prj')
        source_project = content.get('ref_profile_prj')

        meta = build.get_meta(source_project)
        config = self.change_project_config(
            build, base_ref_project, source_project, tag_name)
        project_name = "%s:%s" % (ref_name, tag_name)
        content['obs_url'] = os.path.join(os.getenv('OBS_URL_EXTERNAL'), 'project/show?project=%s' % project_name)

        self.create_basechecker_project(
            build, project_name, content, meta=meta, config=config, baserefproject=base_ref_project, linked='all')

        request_submits = self.sort_by_request_submit(
            content.get("request_sr"), "project")

        self.copy_request_submits_package(build, project_name, request_submits)

        for idx, val in enumerate(request_submits):
            if val and request_submits[idx]:
                self.create_link_aggregate_package(
                    build, base_ref_project, project_name, request_submits[idx])

        self.post_project_create(content, project_name)

    def create_basechecker_project(self, build, target, info, meta=None, config=None, baserefproject=None, linked=''):
        """
        create project
        """
        try:
            if not build.exists(target):
                try:
                    build.create_project(
                        target, None, description=json.dumps(info))
                except ObsError, error:
                    raise LocalError(
                        "Unable to create project %s: %s" % (target, error))

                if meta:
                    # set meta
                    xml_meta = ElementTree.fromstringlist(meta)
                    # change the target project name
                    xml_meta.set('name', target)
                    # delete remote person
                    for person_element in xml_meta.findall('person'):
                        xml_meta.remove(person_element)

                # delete link project
                for link in xml_meta.findall('link'):
                    xml_meta.remove(link)

                # replace
                if baserefproject:
                    for repo_element in xml_meta.findall('repository'):
                        if linked:
                            repo_element.set('linkedbuild', linked)
                        for element in repo_element.findall('path'):
                            element.set('project', baserefproject)

                # add link project
                element = ElementTree.Element(
                    'link', {"project": "%s" % (baserefproject)})
                xml_meta.append(element)
                # add target person
                element = ElementTree.Element('person', {"userid": "%s" % (
                    os.getenv("OBS_API_USERNAME")), "role": "maintainer"})
                xml_meta.append(element)
                build.set_meta(ElementTree.tostring(xml_meta), target)
                # set project config
                build.set_project_config(target, config)
                # set project description
                build.set_description(json.dumps(info), target)
                # set build & publish flag enable
                build.set_global_flag('build','enable', target)
                build.set_global_flag('publish','enable', target)

                print "\nTarget project %s created" % (target)
                return True
            else:
                print "\nTarget project %s exist" % (target)
                return False
        except ObsError, error:
            raise LocalError("Unable to create project %s: %s" %
                             (target, error))

    def sort_by_request_submit(self, request_submits, key):
        return sorted(request_submits, key=lambda submit: (submit[key]))

    def copy_request_submits_package(self, build, project_name, request_submits):
        for submit in request_submits:
            src_project = submit['project']
            for package in submit['packages'].split(','):
                build.create_copy_pac(
                    src_project, package, project_name, package)

    def add_multi_link_project(self, build, project, link_project):
        """ add link project """
        path = core.quote_plus(project)
        kind = 'prj'
        data = core.meta_exists(
            metatype=kind, path_args=path, template_args=None, create_new=False)
        if not data:
            return

        root = ElementTree.fromstring(''.join(data))
        root.insert(2, ElementTree.Element('link', project=link_project))

        res_data = ElementTree.tostring(root)
        build.set_meta(res_data, project)

    def create_link_aggregate_package(self, build, src_project, dst_project, target_package):
        sourceinfo = build.get_sourceinfo_list(src_project)
        for package in sourceinfo:
            if sourceinfo[package]:
                link_prj, link_pkg = sourceinfo[package][-1].split('/')
                if link_prj == src_project and link_pkg == target_package:
                    build.create_link_pac(
                        dst_project, target_package, dst_project, package)

            if re.search("_aggregate", package):
                print("Copypac aggregate package: %s/%s" %
                      (dst_project, package))
                build.create_copy_pac(
                    src_project, package, dst_project, package)
                aggregate_file_name = "_aggregate"
                build.get_source_file(
                    src_project, package, aggregate_file_name)
                content = ""
                with open(aggregate_file_name, 'r') as f:
                    content = f.read()

                if not re.search("qemu_aggregate", package) and \
                   not re.search("java-1_6_0-sun_aggregate", package) and \
                   not re.search("jpackage-utils_aggregate", package):
                    content_xml_root = ElementTree.fromstringlist(content)
                    for element in content_xml_root.findall('aggregate'):
                        #TODO: Set aggregate project only for matched project
                        if element.get('project') == src_project:
                            element.set('project', dst_project)
                    content = ElementTree.tostring(content_xml_root)
                    with open(aggregate_file_name, 'w') as f:
                        f.write(content)
                    commit_msg = "uploaded to copy pac %s/%s from %s" % \
                                 (dst_project, package, src_project)
                    try:
                        print src_project, dst_project, package
                        build.commit_files(dst_project, package,
                                           [(aggregate_file_name, True)], commit_msg)
                    except ObsError, error:
                        raise UploadError("Unable to upload _aggregate to %s: %s" % \
                             (dst_project, error))

                print("aggregate Copy Done.")

    def check_build_status(self, build, project):
        sleep(30)
        status = build.getbuildstatus(project)

        for repo in status.get('buildstatus'):
            if repo.get('code') != 'succeeded':
                return False

        return True

    def add_linked_project_profile(self, obs_api, obs_user, obs_passwd):
        build = BuildService(obs_api, obs_user, obs_passwd)
        content = trigger_info(os.getenv("TRIGGER_INFO"))
        project = content.get("project") or content.get('sourceproject')

        disc = build.get_description(project)
        acceptable_string_disc = disc.replace("'", "\"")
        info = json.loads(acceptable_string_disc)
        source_project = info.get('ref_profile_prj')
        event_type = content.get("event_type")

        checked_repos = self.check_build_status(build, project)

        if not checked_repos:
            print("Please Check build status. !!")
            trigger_next('#PRERELEASE#%s#%s' % (project, event_type), content)
            return

        if build.exists(project):
            self.remove_repos_path_project(build, project)
            if not checked_repos:
                print("Please Check build status. !!")
                trigger_next('#PRERELEASE#%s#%s' % (project, event_type), content)
                return
            else:
                try:
                    self.add_multi_link_project(build, project, source_project)
                    info["build_step"] = "profile_check"
                    build.set_description(json.dumps(info), project)
                except ObsError, error:
                    raise LocalError("Can't add link project : %s" % (error))

        else:
            print("No search Project %s" % (project))

    def remove_repos_path_project(self, build, project_name):

        path = core.quote_plus(project_name)
        kind = 'prj'
        data = core.meta_exists(
            metatype=kind, path_args=path, template_args=None, create_new=False)

        if not data:
            return

        root = ElementTree.fromstring(''.join(data))

        for repo in root.iter('repository'):
            for repo_path in repo.findall('path'):
                repo.remove(repo_path)

        res_data = ElementTree.tostring(root)
        build.set_meta(res_data, project_name)

    def create_project(self, build, project_name, info, base_ref_project, source_project=None):

        try:
            if not build.exists(project_name):
                try:
                    build.create_project(
                        project_name, src=source_project, description=json.dumps(info), linkto=base_ref_project, linkedbuild='all')
                except ObsError, error:
                    raise LocalError(
                        "Unable to create project %s: %s" % (project_name, error))

            else:
                print "\nTarget project %s exist" % (project_name)
                return False
        except ObsError, error:
            raise LocalError("Unable to create project %s: %s" %
                             (project_name, error))

    def main(self, action=None):
        """
        main
        """
        obs_api = os.getenv("REF_TARGET_OBS_API_URL")
        obs_user = os.getenv("REF_TARGET_OBS_API_USERNAME")
        obs_passwd = os.getenv("REF_TARGET_OBS_API_PASSWD")

        remote_obs_api = os.getenv("REF_REMOTE_OBS_API_URL")
        remote_obs_user = os.getenv("REF_REMOTE_OBS_API_USERNAME")
        remote_obs_passwd = os.getenv("REF_REMOTE_OBS_API_PASSWD")

        info = trigger_info(os.getenv('TRIGGER_INFO'))
        for loop in range(1):
            if action == 'base_check' or action is None:

                for field in ('obs_target_prj', 'build_step', 'virtual_sr', 'ref_base_prj', 'ref_profile_prj', 'request_sr', 'base_target_prj'):
                    if field not in info:
                        print 'Error: TRIGGER_INFO doesn\'t contain %s' % field
                        return -1
                return self.run_create_staging_base_project_obs(obs_api, obs_user, obs_passwd, remote_obs_api, remote_obs_user, remote_obs_passwd)
            elif action == 'profile_check':
                return self.add_linked_project_profile(obs_api, obs_user, obs_passwd)
            else:
                print('not enable action = %s' % (action))

        return True


if __name__ == '__main__':
    trigger = CreateProjectObs()
    sys.exit(trigger.main(sys.argv[1]))
