#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright (c) 2014, 2015, 2016 Samsung Electronics.Co.Ltd.
#
# 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.
#
"""
This code is called by jenkins jobs triggered by OBS events.
"""

import os
import sys
import re
import shutil
import base64
import datetime
sys.path.insert(1, os.path.join(sys.path[0], '..'))
from common.repomaker import find_files, RepoMaker, RepoMakerError
from common.buildtrigger import trigger_info, trigger_next
from common.buildservice import BuildService
from common.snapshot import Snapshot, SnapshotError
from common.backenddb import BackendDB
from common.trbs import get_info_from_trbs_name, get_trbs_project_name
from common.iris_rest_client import IrisRestClient

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

def update_ks(imagedata, snapshot_id, pkg_urls, repo_name, base_project=None):
    """
    update the repo url point to right URL and add trbs 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=trbs --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)
                    #TODO: Change snapshot server from /snapshots/ to /public_mirror/
                    if '/snapshots/' in line:
                        line = line.replace('/snapshots/', '/public_mirror/')
            elif line.startswith('repo ') and \
                    'baseurl=' in line:
                match = re.match(r"repo --name=([\w\-\.]*) --baseurl=.*",
                                 line)
                if match:
                    #TODO: Change snapshot server from /snapshots/ to /public_mirror/
                    if '/snapshots/' in line:
                        line = line.replace('/snapshots/', '/public_mirror/')
                #BASE replace
                if base_project and base_project != 'latest':
                    match = re.match(r"repo --name=base([\w\-\.]*) --baseurl=.*/latest/repos/.*",
                                     line)
                    if match:
                        base_path = [ x for x in base_project.lower().split(':') if x != 'ref' ]
                        line = line.replace('/latest/', '/%s/' % ('-'.join(base_path[0:-1]) + '_' + base_path[-1]))
                print 'line=%s' %(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 trbs 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 get_parent_project_name(target_project):
    return target_project.split(':ref')[0]

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

    Args:
        project (str): OBS trbs 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
    """

    print 'MAKE_REPO(project=%s, repo=%s, base_url=%s, base_path=%s, live_repo_base=%s)' \
          % (project, repo, base_url, base_path, live_repo_base)
    images_count = 0
    create_repo = []
    repos = {}
    imagedatas = {}

    # Make build id from latest snapshot + project suffix
    target_project, basebuildid, tstamp = get_info_from_trbs_name(project)
 
    try:
        snapshot = Snapshot(backenddb, base_path, obs_project=target_project)
    except SnapshotError, err:
        raise LocalError("Error getting snapshot info: %s" % str(err))

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

    print "basebuildid: %s" % basebuildid
    print "trbs.build_id: %s" % trbs.build_id
    print "trbs.path: %s" % trbs.path
    print "trbs.dir: %s" % trbs.dir
    print "trbs.snap_buildid: %s" % trbs.snap_buildid

    # Convert live repo to download structure
    live_repo_path = os.path.join(live_repo_base,
                                  project.replace(':', ':/'))
    repo_dir = os.path.join(trbs.path, "repos")

    print "live_repo_path: %s" % live_repo_path
    print "repo_dir: %s" % repo_dir

    snapshot_path = os.path.join(snapshot.base_path, trbs.dir).replace('trbs', 'public_mirror')

    print 'snpashot_pth: %s' %(snapshot_path)

    targets = snapshot.targets
    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
    base_project = build_info.get('base', 'latest')
    print 'BASE PROJECT : %s' % (base_project)

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

    if buildmonitor_enabled:
        global bm_snapshot_name
        global bm_snapshot_url
        bm_snapshot_name = trbs.build_id
        bm_snapshot_url = os.path.join(base_url, trbs.dir, trbs.build_id)

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

        repomaker = RepoMaker(trbs.build_id,
                              os.path.join(trbs.path,
                                           trbs.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)

        # 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 trbs 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" %
                #        os.path.join(snapshot_path, 'repos', repo['Name']))
                print "Image configuration not found in %s" \
                      % os.path.join(snapshot_path, 'repos', repo['Name'])
                continue

            for rpm in img_conf_list:
                repomaker.load_imagedata(rpm)

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

        # trigger post snapshot creation job with repo data
        # buildlogs.job
        imagedatas[repo['Name']] = repomaker.imagedata
        repos.update(repomaker.repos)

        trbs_pkg_urls = trbs.pkg_urls(repo['Name'])

        print trbs_pkg_urls

        # Update ks files
        images_ks = update_ks(repomaker.imagedata, 
                              trbs.snap_buildid,
                              trbs.pkg_urls(repo['Name']),
                              repo['Name'],
                              base_project)

        if buildmonitor_enabled:
            bm_pkg_urls_dic = trbs.pkg_urls(repo['Name'])
            print '[%s] trbs.pkg_urls(%s), base_path(%s)\n' \
                  % (__file__, bm_pkg_urls_dic, base_path)
            bm_repo = repo['Name']
            bm_arch = repo['Architectures'][0]
            bm_pkg_url = bm_pkg_urls_dic[bm_arch]
            bm_pkg_dir = bm_pkg_url.replace(base_url, base_path)
            #print '[%s] bm_arch(%s), bm_pkg_dir(%s), os.listdir(bm_pkg_dir)(%s)\n' \
            #      % (__file__, bm_arch, bm_pkg_dir, 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_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, trbs.build_id,
                               os.path.join(trbs.dir,trbs.build_id),
                               project, base_url, repo['Name'], download_num=current_download_num)

    build.update_info({'images_count': images_count,
                       'images': [],
                       'download_url':os.path.join(base_url, trbs.dir, trbs.build_id),
                       'download_num': int(1)
                      }, project)

    #TODO: IMAGER want to read download_num to give up; rsync operation.
    try:
        with open(os.path.join(trbs.path, trbs.build_id, 'buildinfo.in'), 'w') as df:
            df.write('download_num=%d\n' % current_download_num)
    except Exception, err:
        print 'not to update download_num note. %s' % str(err)

    # trigger post snapshot creation job with repo data
    # buildlogs.job
    data = {'project': project,
        'repo': repos,
        'repo_path': os.path.join(trbs.dir, trbs.build_id),
        'build_id': trbs.build_id
    }
    parm_backend = {}
    for bknd in list(reversed(range(1,99))):
        if os.getenv('BACKEND_%02d_REGEX' % bknd) and \
            re.search(r'%s' % os.getenv('BACKEND_%02d_REGEX' % bknd), data['project']) is not None:
            parm_backend['BACKEND_SELECTION'] = 'BACKEND_%02d' % bknd
            break
    trigger_next("post-buildlogs", data, extra_params=parm_backend)

    return 1

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

    # Event is from project delted
    trbs_project_name = event_dict.get("project") or\
        event_dict.get("sourceproject")

    try:
        target_project, basebuildid, time_stamp = \
            get_info_from_trbs_name(trbs_project_name)
    except ValueError:
        print "Can not get trbs project info from project name," \
            "take no action to %s" % trbs_project_name

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

    if os.path.isdir(os.path.join(trbs.path, trbs.build_id)):
        shutil.rmtree(os.path.join(trbs.path, trbs.build_id))
        print 'Removing the snapshot project: %s' % os.path.join(trbs.path, trbs.build_id)
        # TRIGGER NEXT SYNC-AWS
        if os.getenv("TRBS_SYNC_AWS_ENABLED", "0") != "0":
            data = {"remove_path": os.path.join(trbs.path, trbs.build_id)}
            trigger_next('SYNC-AWS', data)
    else:
        print 'The snapshot project: %s is not present' % os.path.join(trbs.path, trbs.build_id)

    return

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'
            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)

        if not make_repo(project, repo, backenddb, base_url, base_path, live_repo_base, build, block=bBuildFail):
            return 0
    elif action == 'cleanup':
        # request(SR) end of life, this founction should be called to
        # delete the trbs project "
        project_cleanup(backenddb, build, base_path, base_url, content)
    else:
        raise LocalError("Not supported method of pre_trbs_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-3-%s" % bm_stage, bm_data)

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