#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright (C) 2010, 2011, 2012, 2013, 2014 Intel, 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; version 2
#    of the License.
#
#    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.
#
"""
This code is called by jenkins jobs triggered by OBS events.
"""

import os
import sys
import re
import shutil
import base64
import datetime

from common.repomaker import find_files, RepoMaker, RepoMakerError
from common.buildtrigger import trigger_info, trigger_next
from common.buildservice import BuildService
from common.backenddb import BackendDB
from common.prerelease import get_info_from_prerelease_name
from common.snapshot import Snapshot, SnapshotError
from common.iris_rest_client import IrisRestClient

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

def update_ks(imagedata, snapshot_id, pkg_urls):
    """
    update the repo url point to right URL and add prerelease repo
    url with highest priority
    Args:
         imagedata (ImageData): [(ks_file_name, ks_file_content),]
         backenddb (BackendDB): backend database instance
         data: data from backend db
    """

    # Figure out repo line
    repo_lines = {}
    for arch, url in pkg_urls.iteritems():
        repo_lines[arch] = "repo --name=prerelease --baseurl=%s --save "\
                          "--ssl_verify=no --priority 1" % url
    images_ks = {}
    # update ULRs in ks file
    for name, content in imagedata.ksi.items():
        repo_line = repo_lines.get(imagedata.images[name]['arch'])
        if not repo_line:
            # skip architectures without repos
            continue
        new_ks_lines = []
        for line in content.splitlines():
            if line.startswith('repo ') and \
                    'baseurl=' in line and \
                    '@BUILD_ID@' in line:
                match = re.match(r"repo --name=([\w\-\.]*) --baseurl=.*",
                                 line)
                if match:
                    line = re.sub(r'@BUILD_ID@', snapshot_id, line)
            new_ks_lines.append(line)

        new_ks_lines.insert(new_ks_lines.index('%packages')-1, repo_line)
        # Update the ks files in imagedata
        new_ks_content = '\n'.join(new_ks_lines)
        images_ks[name] = new_ks_content

    return images_ks

def trigger_image_creation(images_ks, build_id, path_repo,
                           project, url_pub_base, repo_name, download_num=1):
    """
    trigger_image_creation:
        Prepare the data and trigger the image_creation jobs
    Args:
         images_ks (truple list): [(ks_file_name, ks_file_content),]
         build_id (str): the prerelease repo build_id
    """
    count = 0
    for index, (ksname, kickstart) in enumerate(images_ks.items()):
        count += 1
        name = ksname.replace('.ks', '')
        data = {'name': name,
                'kickstart': kickstart,
                'buildid': build_id,
                'images_path': os.path.join("images", repo_name, name),
                'project': project,
                'repo_path': path_repo,
                'repo': repo_name,
                'url_pub_base': url_pub_base,
                'download_num': download_num
                }
        # add image_building for iris
        pub_enabled = os.getenv("IRIS_PUB_ENABLED", "0") != "0"
        if pub_enabled:
            rest = IrisRestClient(
                os.getenv("IRIS_SERVER"),
                os.getenv("IRIS_USERNAME"),
                base64.b64decode(os.getenv('IRIS_PASSWORDX', '')))
            rest.publish_event("image_building", {
                "project": project,
                "repo": repo_name,
                "name": name,
                })
        trigger_next('%s/image_trigger_%s_%s' % (os.getenv('WORKSPACE'),
                                                 repo_name, index), data)
    return count

def make_repo(project, repo, backenddb, base_url, base_path,
              live_repo_base, build, block=False):
    """
    make repo.

    Args:
        project (str): OBS prerelease project name
        repo (str): name of the OBS live repository
        backenddb (BackendDB): backenddb instance
    Raises:
        LocalError if can't create repos or can't find image configurations
    """
    images_count = 0

    build_info = build.get_info(project)
    if build_info.get('download_num'):
        current_download_num = build_info.get('download_num') + 1
    else:
        current_download_num = int(1)
    print 'Current download_num = %d' % current_download_num

    # Make build id from latest snapshot + project suffix
    target_project, tstamp = get_info_from_prerelease_name(project)

    repos = {}
    imagedatas = {}

    try:
        snapshot = Snapshot(backenddb, base_path, obs_project=target_project)
    except SnapshotError, err:
        raise LocalError("Error getting snapshot info: %s" % str(err))

    try:
        prerelease = snapshot.get_prerelease(base_url, tstamp)
    except SnapshotError, err:
        raise LocalError("Error getting prerelease info: %s" % str(err))

    # Convert live repo to download structure
    live_repo_path = os.path.join(live_repo_base,
                                  project.replace(':', ':/'))
    targets = snapshot.targets

    # Cleanup repo directory
    if os.path.isdir(os.path.join(prerelease.path, prerelease.build_id)):
        print " repo path=%s exits. Cleanup repo directory" % os.path.join(prerelease.path, prerelease.build_id)
        shutil.rmtree(os.path.join(prerelease.path, prerelease.build_id))

    if buildmonitor_enabled:
        global bm_snapshot_name
        global bm_snapshot_url
        bm_snapshot_name = prerelease.build_id
        bm_snapshot_path = prerelease.path
        bm_snapshot_url = os.path.join(bm_snapshot_path.replace(base_path, base_url),
                                       bm_snapshot_name)

    for repo in targets:
        if block:
            repos.update({repo['Name']: {'archs': list(set(repo['Architectures']))}})
            continue

        repomaker = RepoMaker(prerelease.build_id,
                              os.path.join(prerelease.path,
                                           prerelease.build_id))
        try:
            repomaker.add_repo(live_repo_path, repo['Name'],
                               repo['Architectures'], move=False)
        except RepoMakerError, err:
            raise LocalError("Unable to create download repo: %s" % err)

        repos.update(repomaker.repos)
        imagedatas[repo['Name']] = repomaker.imagedata

        # Assuming that there can be just one image-configurations-
        # rpm in the repo
        if not repomaker.has_images():
            # repomaker did not found image-configurations in pre_release repo,
            # let's take it from target repo, only one package repo is enough

            # Add image configuration to prerelease repo
            img_conf = find_files(os.path.join(snapshot.path, 'repos',
                                               repo['Name']),
                                  prefix="image-configurations-",
                                  suffix='noarch.rpm')
            img_conf_list = list(img_conf)
            # whether exist package of image-configuration
            if not img_conf_list:
                if buildmonitor_enabled:
                    print '[%s][LocalError] bm_git_tag(%s)\n' % (__file__, bm_git_tag)
                    #buildmonitor.update_fail_status_for_sr_stage(project, bm_git_tag)
                    bm_stage = 'Pre_Snap_Fail'
                    bm_data = {"bm_stage" : bm_stage,
                               "project" : project,
                               "bm_git_tag" : bm_git_tag,
                              }
                    trigger_next("BUILD-MONITOR-4-%s" % bm_stage, bm_data)

                #raise LocalError("Image configuration not found in %s" %
                #        snapshot.path)
                print "Image configuration not found in %s" %( snapshot.path)
                continue

            for rpm in img_conf_list:
                repomaker.load_imagedata(rpm)

        # whether exist ks poin to the repo
        if not repomaker.has_images():
            continue

        # Update ks files
        images_ks = update_ks(repomaker.imagedata, snapshot.build_id,
                              prerelease.pkg_urls(repo['Name']))


        if buildmonitor_enabled:
            bm_pkg_urls_dic = prerelease.pkg_urls(repo['Name'])
            print '[%s] prerelease.pkg_urls(%s), base_path(%s)\n' \
                  % (__file__, bm_pkg_urls_dic, base_path)
            bm_repo = repo['Name']             # emul32-wayland / emul64-wayland / arm-wayland / arm64-wayland / target-TM1
            bm_arch = repo['Architectures'][0] # ia32 / x86_64 / armv7l / aarch64 / armv7l
            bm_pkg_url = bm_pkg_urls_dic[bm_arch]
            bm_pkg_dir = bm_pkg_url.replace(base_url, base_path) #http -> /srv/obs
            #print '[%s] bm_arch(%s), os.listdir(bm_pkg_dir)(%s)\n' \
            #      % (__file__, bm_arch, os.listdir(bm_pkg_dir))

            if bm_arch == 'ia32':
                bm_arch = 'i686'

            # get rpm files
            bm_pkg_name_lst = []
            bm_pkg_mdate_lst = []
            bm_pkg_size_lst = []
            bm_trg_count = 0
            bm_pkg_count = 0
            BM_PKG_LIMIT = 1100
            for root, dirs, files in os.walk(bm_pkg_dir):
                for each_file in files:
                    if each_file.endswith(".rpm"):
                        rpm_file_path = root + '/' + each_file
                        rpm_file_mdate = os.path.getmtime(rpm_file_path)
                        rpm_file_size = os.path.getsize(rpm_file_path)
                        bm_pkg_name_lst.append(each_file)
                        bm_pkg_mdate_lst.append(rpm_file_mdate)
                        bm_pkg_size_lst.append(rpm_file_size)
                        #print '[%s] rpm_file_path(%s), rpm_file_mdate(%s), rpm_file_size(%s)\n' \
                        #      % (__file__, rpm_file_path, rpm_file_mdate, rpm_file_size)

                        # divide the big pkgs
                        bm_pkg_count += 1
                        #print '[%s] bm_pkg_count(%s), BM_PKG_LIMIT(%s)\n' \
                        #      % (__file__, bm_pkg_count, BM_PKG_LIMIT)
                        if bm_pkg_count >= BM_PKG_LIMIT:
                            # for trigger
                            bm_stage = 'Pre_Snap_packages'
                            bm_data = {"bm_stage" : bm_stage,
                                       "project" : project,
                                       "bm_repo" : bm_repo,
                                       "bm_arch" : bm_arch,
                                       "bm_pkg_url" : bm_pkg_url,
                                       "bm_pkg_name_lst" : bm_pkg_name_lst,
                                       "bm_pkg_mdate_lst" : bm_pkg_mdate_lst,
                                       "bm_pkg_size_lst" : bm_pkg_size_lst,
                                       "bm_trg_count" : bm_trg_count,
                                       "bm_pkg_count" : bm_pkg_count,
                                       "BM_PKG_LIMIT" : BM_PKG_LIMIT,
                                      }
                            trigger_next("BUILD-MONITOR-2-%s-%s-%s-trg_%s" % (bm_stage, bm_repo, bm_arch, bm_trg_count), bm_data)

                            # clear the data
                            bm_pkg_count = 0
                            bm_trg_count += 1
                            bm_pkg_name_lst = []
                            bm_pkg_mdate_lst = []
                            bm_pkg_size_lst = []
                            #print '[%s] reach the BM_PKG_LIMIT!!(%s), bm_pkg_count(%s), bm_trg_count(%s)\n' \
                            #      % (__file__, BM_PKG_LIMIT, bm_pkg_count, bm_trg_count)

            # for rest pkgs
            #buildmonitor.create_snapshot_packages_for_build_snapshot_package(project, bm_snapshot_name,
            #                                                                 repo['Name'], repo['Architectures'][0],
            #                                                                 bm_pkg_urls_dic, base_url, base_path)
            bm_stage = 'Pre_Snap_packages'
            bm_data = {"bm_stage" : bm_stage,
                       "project" : project,
                       "bm_repo" : bm_repo,
                       "bm_arch" : bm_arch,
                       "bm_pkg_url" : bm_pkg_url,
                       "bm_pkg_name_lst" : bm_pkg_name_lst,
                       "bm_pkg_mdate_lst" : bm_pkg_mdate_lst,
                       "bm_pkg_size_lst" : bm_pkg_size_lst,
                       "bm_trg_count" : bm_trg_count,
                       "bm_pkg_count" : bm_pkg_count,
                       "BM_PKG_LIMIT" : BM_PKG_LIMIT,
                      }
            #print '[%s] for rest pkgs!! BM_PKG_LIMIT(%s), bm_pkg_count(%s), bm_trg_count(%s)\n' \
            #      % (__file__, BM_PKG_LIMIT, bm_pkg_count, bm_trg_count)
            trigger_next("BUILD-MONITOR-2-%s-%s-%s-trg_%s" % (bm_stage, bm_repo, bm_arch, bm_trg_count), bm_data)

        # Generate image info to builddata/ dir
        repomaker.gen_image_info(images_ks)

        # trigger image creation jobs
        images_count += trigger_image_creation(images_ks, prerelease.build_id,
                               os.path.join(prerelease.dir,
                                            prerelease.build_id),
                               project, base_url, repo['Name'], download_num=current_download_num)
    # reset 'images' and add download_url meta info
    build.update_info({'images_count': images_count,
                       'images': [],
                       'download_url': os.path.join(base_url,
                                                    prerelease.dir,
                                                    prerelease.build_id),
                       'download_num': int(1)},
                       project)

    return {'project': project,
            'repo' : repos,
            'repo_path': os.path.join(prerelease.dir, prerelease.build_id),
            'build_id': prerelease.build_id,
            'imagedata': imagedatas}


def get_published_repos(repo_path_base, time_stamp):
    """ Search publised repository recursively
    Args:
         repo_path_base (str): path to base dir
         time_stamp (str): time stamp of sbumit tag
    """
    if os.path.isdir(repo_path_base):
        for name in os.listdir(repo_path_base):
            path = os.path.join(repo_path_base, name)
            if os.path.isdir(path):
                if name.endswith(time_stamp):
                    if len(os.listdir(repo_path_base)) <= 1:
                        yield repo_path_base
                    else:
                        yield path
                else:
                    for npath in get_published_repos(path, time_stamp):
                        yield npath

def project_cleanup(backenddb, build, base_path, base_url, event_dict):
    """ request(SR) end of life, this founction should be called to
    delete the prerelease project """

    # Event is from project delted or from request accepted/rejected
    prerelease_project_name = event_dict.get("project") or\
        event_dict.get("sourceproject")

    try:
        target_project, time_stamp = \
            get_info_from_prerelease_name(prerelease_project_name)
    except ValueError:
        print "Can not get prerelease project info from project name," \
            "take no action to %s" % prerelease_project_name
        return 0

    # Delete the prerelease project
    build.cleanup(prerelease_project_name, "Cleaned up by backend automaticly")

    # Get prerelease data from db
    try:
        snapshot = Snapshot(backenddb, base_path, obs_project=target_project)
        prerelease = snapshot.get_prerelease(base_url, time_stamp)
    except SnapshotError, err:
        raise LocalError("Error getting prerelease data: %s" % str(err))

    # remove latest snapshot from prerelease_path as we need to clean up
    # published repos from old snapshots too
    root = prerelease.path.split(prerelease.snapshot.build_id)[0]

    for path in get_published_repos(root, time_stamp):
        print 'Removing the repo: %s' % path
        shutil.rmtree(path)

def project_src_rpm_cleanup(live_repo_base, project):
    """ clean src.rpm files on project
    Args:
         live_repo_base (str): path to live repo base dir
         project (str): project name
    """

    print "==========project_src_rpm_cleanup============"
    live_path = os.path.join(live_repo_base, project.replace(':', ':/'))
    print "live_path: %s" % (live_path)

    for root, dirs, files in os.walk(live_path):
        for filename in files:
            if 'src.rpm' in filename:
                print 'rm %s' % (os.path.join(root, filename))
                os.remove(os.path.join(root, filename))

def check_build_fail(unresolvable_broken_failed_status):
    for repo in unresolvable_broken_failed_status:
        for arch in unresolvable_broken_failed_status[repo]:
            for p in unresolvable_broken_failed_status[repo][arch]:
                if p[-10:] != "_aggregate":
                    return True
    return False

def get_unresolvable_broken_packages(unresolvable_broken_failed_status):
    unresolvable_broken_packages = {}

    for repo in unresolvable_broken_failed_status:
        unresolvable_broken_packages[repo] = {}
        for arch in unresolvable_broken_failed_status[repo]:
            unresolvable_broken_packages[repo][arch] = {}
            for p in unresolvable_broken_failed_status[repo][arch]:
                if unresolvable_broken_failed_status[repo][arch][p] in ("unresolvable", "broken"):
                    unresolvable_broken_packages[repo][arch][p] = unresolvable_broken_failed_status[repo][arch][p]

    return unresolvable_broken_packages

def main(action):
    """Script entry point.
       Parameters:
          action - cleanup or create_images
    """

    print '---[JOB STARTED: %s ]-------------------------' % action
    global buildmonitor_enabled
    buildmonitor_enabled = os.getenv("BUILDMONITOR_ENABLED", "0") != "0"
    print 'buildmonitor_enabled(%s)\n' % (buildmonitor_enabled)
    if buildmonitor_enabled:
        bm_start_datetime = datetime.datetime.now()
        global bm_git_tag # for execption handling
        bm_git_tag = None

    obs_api = os.getenv("OBS_API_URL")
    obs_user = os.getenv("OBS_API_USERNAME")
    obs_passwd = os.getenv("OBS_API_PASSWD")
    base_url = os.getenv("URL_PUBLIC_REPO_BASE")
    base_path = os.getenv('PATH_REPO_BASE')
    live_repo_base = os.getenv('PATH_LIVE_REPO_BASE')

    content = trigger_info(os.getenv("TRIGGER_INFO"))

    project = content.get("project") or content.get("sourceproject")

    build = BuildService(obs_api, obs_user, obs_passwd)

    # Init backend database
    backenddb = BackendDB(os.getenv("REDIS_HOST"), int(os.getenv("REDIS_PORT")))

    if action == 'create_images':
        repo = content.get("repo")
        info = build.get_info(project)

        buildstatus = build.getbuildstatus(project)
        print 'buildstatus=%s' %(buildstatus)
        build.update_buildstatus(buildstatus,project)
        global bBuildFail

        unresolvable_broken_failed_status = build.get_package_build_result(project, ("unresolvable", "broken", "failed"))
        bBuildFail = check_build_fail(unresolvable_broken_failed_status)

        if buildmonitor_enabled:
            bm_git_tag = info['git_tag']
            #buildmonitor.start_pre_create_snapshot_for_sr_stage(project, bm_git_tag, bm_start_datetime)
            #buildmonitor.start_pre_create_snapshot_for_build_snapshot(project, bm_start_datetime)
            bm_stage = 'Pre_Snap_Start' # (+Pre_Snap_snapshot)
            bm_data = {"bm_stage" : bm_stage,
                       "project" : project,
                       "bm_git_tag" : bm_git_tag,
                       "bm_start_datetime": str(bm_start_datetime),
                       "bBuildFail": bBuildFail,
                       "unresolvable_broken_packages": get_unresolvable_broken_packages(unresolvable_broken_failed_status)
                      }
            trigger_next("BUILD-MONITOR-1-%s" % bm_stage, bm_data)

        # cleanup src.rpm files on Live repo
        # we don't used src.rpm files on prerelease project.
        project_src_rpm_cleanup(live_repo_base,project)

        repo_data = make_repo(project, repo, backenddb, base_url, base_path,
                   live_repo_base, build, block=bBuildFail)

        # trigger post snapshot creation job with repo data
        data = repo_data.copy()
        # remove unused item
        data.pop('imagedata')

        trigger_next("post-snapshot", data)
    elif action == 'cleanup':
        # request(SR) end of life, this founction should be called to
        # delete the prerelease project "
        project_cleanup(backenddb, build, base_path, base_url, content)
    else:
        raise LocalError("Not supported method of pre_release_obs job: %s" \
                          % action)

    if buildmonitor_enabled and action == 'create_images' and bBuildFail != True:
        info = build.get_info(project)
        bm_snapshot_num = info['download_num']
        print '[%s] bm_snapshot_num(%s)\n' \
              % (__file__, bm_snapshot_num)
        bm_end_datetime = datetime.datetime.now()
        #print '[%s] project(%s), bm_git_tag(%s), start_time(%s), end_time(%s)\n' \
        #      % (__file__, project, bm_git_tag, bm_start_datetime, bm_end_datetime)
        # for sr_stage & build_snapshot
        #buildmonitor.end_pre_create_snapshot_for_sr_stage(project, bm_git_tag,
        #                                                  bm_start_datetime,
        #                                                  bm_end_datetime)
        #buildmonitor.end_pre_create_snapshot_for_build_snapshot(project,
        #                                                        bm_snapshot_name,
        #                                                        bm_snapshot_url,
        #                                                        bm_end_datetime)
        bm_stage = 'Pre_Snap_End'
        bm_data = {"bm_stage": bm_stage,
                   "project" : project,
                   "bm_git_tag": bm_git_tag,
                   "bm_start_datetime": str(bm_start_datetime),
                   "bm_end_datetime": str(bm_end_datetime),
                   "bm_snapshot_name" : bm_snapshot_name,
                   "bm_snapshot_url" : bm_snapshot_url,
                   "bm_snapshot_num" : bm_snapshot_num,
                  }
        trigger_next("BUILD-MONITOR_%s" % bm_stage, bm_data)

if __name__ == '__main__':
    try:
        sys.exit(main(sys.argv[1]))
    except LocalError, error:
        print error
        sys.exit(1)
