#!/usr/bin/env python

import os
import sys
import base64
import json
import re
import shutil
import tempfile
import traceback
import urllib2
import xml.etree.ElementTree as ET
import ast
from common import gerrit
from common import git
from common import utils
from common import runner
from common.buildtrigger import trigger_info, trigger_next
from common.send_mail import prepare_mail
from time import sleep

from common.workflow import MailSender
from common.prerelease import get_tagname_from_prerelease_name

GIT_URL = 'ssh://%s@%s:%s' % (os.getenv('GERRIT_USERNAME'),
                              os.getenv('GERRIT_HOSTNAME'),
                              os.getenv('GERRIT_SSHPORT'))
HIGHLIGHT = '+'
GREYEDOUT = '-'

class GitUpdateError(Exception): pass
class RevisionError(Exception): pass
class UpstreamError(Exception): pass
class OverwriteError(Exception): pass
class MergeError(Exception): pass
class SubmitError(Exception): pass
class SubmitRequestError(Exception): pass

def submit_request(project, branch, commit, tagname):
    """
    Submit Request
    """
    result = True
    message = "Auto Sync SR Tag"
    try:

        tmpdir = tempfile.mkdtemp(prefix=os.getenv('WORKSPACE')+'/')
        if not git.clone_gitproject(project, os.path.join(tmpdir, project)):
            raise SubmitRequestError('Submit Request git update failed(%s)' % (project))

        git_dir = os.path.join(tmpdir, project)
        gitprj = git.Git(git_dir)

        gitprj.set_branch(branch)
        print 'set branch'
        gitprj.checkout(commit)
        print 'checkout'
        gitprj.create_tag(tagname, message)
        print 'create_tag'
        gitprj.push_tag("origin", tagname)
        print 'push'
    except Exception as err:
        result = False
 
    return result

def submit_gerrit_change(gerritobj, gitprj, project, branch, commit):
    result = True
    gerritobj.review(commit=commit, project=project, message='Submitted by system administrator', verified=1, codereview=2, submit=True)
    gerrit_query = 'project:%s branch:%s commit:%s status:merged' % (project, branch, commit)
    ret = gerritobj.query(gerrit_query)
    if not ret:
        print 'Warning: Submit failed, try direct push...'
        ret = gitprj.push('-q', 'origin', '%s:refs/heads/%s' % (commit, branch))
        if ret == None:
            result = False
    return result

def conv_rev2commit(gitprj, git_dir, revision):
    commit = ''
    try:
        retcode, outs = runner.show('git --git-dir=%s/.git cat-file -t %s' % (git_dir, revision))
        if retcode != 0:
            raise RevisionError('not found object type(%s)' % revision)
        obj_type = outs.strip()
        if obj_type == 'commit' and re.match(r'[0-9a-f]{5,40}', revision):
            commit = revision
        else:
            rev_name = gitprj.name_rev('--name-only %s' % (revision))
            if rev_name == 'undefined':
                rev_name = revision
            commits = gitprj.show('-s', '--format=%H', rev_name)
            if not commits or len(commits) != 1:
                raise RevisionError('not found commit(%s)' % revision)
            commit = commits[0]
    except RevisionError, exc:
        print 'Error:', exc
    return commit

def update_sync_for_git_sync(tmpdir, private_gerrit, target, requests, request_snapshot, sync_user=None):
    """
    Update sync for git sync.
    """

    for item in ('target', 'branch', 'notify', 'fork', 'source_branch'):
        if item not in target:
            print 'Error: target doesn\'t contain %s' % item
            return -1

    target_project = target.get('target') or []
    branch = target.get('branch') or [] # tizen_4.0_mcd_z4
    email_to_members = target.get('notify') or []
    GIT_PREFIX = target.get('fork') or []
    UPSTREAM_BRANCH = "public_" + target.get('source_branch') or []
    tagname = get_tagname_from_prerelease_name(requests.keys()[0])
    email_to_submitters = []
    if sync_user:
        email_to_submitters.append(sync_user)

    request_projects = []
    new_private_projects = []
    overwrite_alreadys = []
    overwrite_successs = []
    upstream_failures = []
    overwrite_failures = []
    merge_failures = []
    merge_successs = []
    submit_failures = []
    submit_request_successs = []
    submit_request_failures = []
    pure_git_sr_list = []

    private_projects = private_gerrit.ls_projects()

    upstream_git_dir = ''
    private_git_dir = ''
    is_direct_review_push = False

    for item  in requests[requests.keys()[0]]:
        project = item.get('gerrit_project')
        revision = item.get('cid')
        submitter = item.get('submitter')
        email_to_submitters.append(submitter)
        git_tag = item.get('git_tag')
        deleted = item.get('deleted')

        if project.startswith(HIGHLIGHT) or deleted:
            pure_git_sr_list.append((project[1:], git_tag))
            continue
        else:
           project = project[1:]
           request_projects.append((project, revision, submitter, git_tag))
           is_mixed_git = True
        try:
            print '\n-----[Manifest Information]-------------------------------'
            print 'GIT_PREFIX:', GIT_PREFIX
            print 'UPSTREAM_BRANCH: ', UPSTREAM_BRANCH
            print 'Project:', project
            print 'Revision:', revision
 
            if not project or not revision:
               raise UpstreamError('not found information(%s, %s)' % ( project, revision))

            print '\n* Update upstream git repo %s' % (project)
            if not git.clone_gitproject(project, os.path.join(tmpdir, project)):
                raise UpstreamError('upstream git update failed(%s)' % (project))

            upstream_git_dir = os.path.join(tmpdir, project)
            upstream_gitprj = git.Git(upstream_git_dir)

            commit = conv_rev2commit(upstream_gitprj, upstream_git_dir, revision)
            if not commit:
                raise UpstreamError('failure to convert revision to commit(%s)' % revision)

            private_project = '%s%s' % (GIT_PREFIX, project)

            if not private_project in private_projects:
                print '\n* Does not exist Private git repo %s' % (private_project)
                #private_gerrit.create_project(aname=private_project, parent='Tizen-Projects', empty_commit=True)
                #private_gerrit.create_project(private_project, '-p', os.getenv('GERRIT_PARENT_PROJECT'))
                new_private_projects.append(private_project)
                continue

            print '\n* Push %s %s %s' % (commit, UPSTREAM_BRANCH, private_project)
            ret = upstream_gitprj._git_inout('push',['-q', '-f', '%s/%s' % (GIT_URL, private_project), \
                                       '%s:refs/heads/%s' % (commit, UPSTREAM_BRANCH)])
            if ret == None:
                raise UpstreamError('git push failed(%s)' % (private_project))

            print '\n* Update private git repo %s' % (private_project)
            if not git.clone_gitproject(private_project, os.path.join(tmpdir, private_project)):
                raise OverwriteError('private git update failed(%s)' % (private_project))

            private_git_dir = os.path.join(tmpdir, private_project)
            private_gitprj = git.Git(private_git_dir)
            # ERROR: missing Change-Id in commit message footer  
            retcode, outs = runner.show('scp -p -P %s %s@%s:hooks/commit-msg %s/.git/hooks/' \
                                        %(os.getenv('GERRIT_SSHPORT'), os.getenv('GERRIT_USERNAME'), 
                                          os.getenv('GERRIT_HOSTNAME'), private_git_dir))
            if retcode != 0:
                raise RevisionError('error')

            if not private_gitprj.has_branch('origin/%s' % branch, True):
                print '\n* Create %s branch on %s repository as %s' % (branch, private_project, commit)
                ret = private_gitprj._git_inout('push',['-q', '-f', '%s/%s' % (GIT_URL, private_project), \
                                       '%s:refs/heads/%s' % (commit, branch)])
                if ret == None:
                    raise UpstreamError('create %s branch failed(%s)' % (branch, private_project))
                merge_successs.append((private_project, '%s -> %s' %(commit, commit)))

                ret = submit_request(project=private_project, branch=branch, commit=commit, tagname=tagname)
                if not ret:
                    raise SubmitRequestError('submit request failed(%s, %s)' % (private_project, commit))
                submit_request_successs.append((private_project, commit, tagname))

            elif is_mixed_git: ## Merge
                if branch in private_gitprj.branch_contains(commit):
                    print 'already merge %s branch..' % (branch)
                    continue

                print '\n* Merge %s %s %s' % (commit, branch, private_project)
                #retcode = private_gitprj.checkout('origin/%s' % branch, '--')[0]
                private_gitprj.checkout('origin/%s' % branch)
                #if retcode != 0:
                #    raise MergeError('not found %s branch(%s)' % (branch, private_project))

                retcode = private_gitprj._exec_git('merge', ['--no-ff', commit])[0]
                if retcode != 0:
                    raise MergeError('merge %s branch failed(%s, %s)' % (branch, private_project, commit))

                retcode, merge_commit = private_gitprj._exec_git('log', ['-n1', '--pretty=%H', 'HEAD', '--'])
                if retcode != 0:
                    raise MergeError('not found merge commit(%s, %s)' % (branch, private_project))

                #print '\n* Commit --amend --no-edit.. '
                ret = private_gitprj._exec_git('commit', ['--amend', '--no-edit'])
                if ret == None:
                    raise OverwriteError('git commit --amend --no-edit failed(%s)' % (private_project))

                ret, outs = private_gitprj._exec_git('log', ['-n1', '--pretty=%H'])
                if ret == None:
                    raise OverwriteError('git log -n1 --pretty=%H' + 'failed(%s)' % (private_project))
                #change overwrite_commit
                merge_commit = outs.strip()
                
                if is_direct_review_push:
                    print '\n* Push %s %s %s' % (merge_commit, branch, private_project)
                    ret = private_gitprj._exec_git('push',['-q', 'origin', '%s:refs/for/%s' % (merge_commit, branch)])
                    if ret == None:
                         raise MergeError('git push failed(%s, %s)' % (private_project, merge_commit))

                    print '\n* Submit', merge_commit
                    ret = submit_gerrit_change(gerritobj=private_gerrit, gitprj=private_gitprj, \
                                               project=private_project, \
                                               branch=branch, commit=merge_commit)
                    if not ret:
                        raise SubmitError('submit failed(%s, %s)' % (private_project, merge_commit))
                    merge_successs.append((private_project, '%s -> %s' %(commit, merge_commit)))
                else:
                    print '\n* Push %s %s %s' % (merge_commit, branch, private_project)
                    ret = private_gitprj._exec_git('push',['-q', 'origin', '%s:refs/heads/%s' % (merge_commit, branch)])
                    if ret == None:
                         raise MergeError('git push failed(%s, %s)' % (private_project, merge_commit))
                    merge_successs.append((private_project, '%s -> %s' %(commit, merge_commit)))

                ret = submit_request(project=private_project, branch=branch, commit=merge_commit, tagname=tagname)
                if not ret:
                    raise SubmitRequestError('submit request failed(%s, %s)' % (private_project, merge_commit))
                submit_request_successs.append((private_project, merge_commit, tagname))

            else: ## Overwrite branch
                print '\n* Overwrite %s %s %s' % (commit, branch, private_project)
                ret, overwrite_commit = private_gitprj.create_overwrite_commit(branch, commit)
                if not ret:
                    raise OverwriteError('overwrite %s branch failed(%s, %s)' % (branch, private_project, commit))

                if not overwrite_commit:
                    print 'already overwrite %s branch..' % (branch)
                    overwrite_alreadys.append((private_project, commit))
                    continue

                print '\n* checkout %s ' %(overwrite_commit)
                private_gitprj.checkout(overwrite_commit)

                #print '\n* Commit --amend --no-edit.. '
                ret = private_gitprj._exec_git('commit', ['--amend', '--no-edit'])
                if ret == None:
                    raise OverwriteError('git commit --amend --no-edit failed(%s)' % (private_project))

                ret, outs = private_gitprj._exec_git('log', ['-n1', '--pretty=%H'])
                if ret == None:
                    raise OverwriteError('git log -n1 --pretty=%H' + 'failed(%s)' % (private_project))
                #change overwrite_commit
                overwrite_commit = outs.strip()

                if is_direct_review_push:
                    print '\n* Push %s %s %s' % (overwrite_commit, branch, private_project)
                    ret = private_gitprj._exec_git('push',['-q', 'origin', '%s:refs/for/%s' % (overwrite_commit, branch)])
                    if ret == None:
                         raise OverwriteError('git push failed(%s)' % (private_project))
                    print '\n* Submit', overwrite_commit
                    ret = submit_gerrit_change(gerritobj=private_gerrit, gitprj=private_gitprj, \
                                               project=private_project, \
                                               branch=branch, commit=overwrite_commit)
                    if not ret:
                        raise SubmitError('submit failed(%s, %s)' % (private_project, overwrite_commit))
                    overwrite_successs.append((private_project, '%s -> %s' %(commit, overwrite_commit)))
                else:
                    print '\n* Push %s %s %s' % (overwrite_commit, branch, private_project)
                    ret = private_gitprj._exec_git('push',['-q', 'origin', '%s:refs/heads/%s' % (overwrite_commit, branch)])
                    if ret == None:
                         raise OverwriteError('git push failed(%s)' % (private_project))
                    overwrite_successs.append((private_project, '%s -> %s' %(commit, overwrite_commit)))

                ret = submit_request(project=private_project, branch=branch, commit=merge_commit, tagname=tagname)
                if not ret:
                    raise SubmitRequestError('submit request failed(%s, %s)' % (private_project, merge_commit))
                submit_request_successs.append((private_project, merge_commit, tagname))

        except UpstreamError, exc:
            upstream_failures.append((project, revision, exc))
            print 'Error:', exc
        except OverwriteError, exc:
            overwrite_failures.append((project, revision, exc))
            print 'Error:', exc
        except MergeError, exc:
            merge_failures.append((project, revision, exc))
            print 'Error:', exc
        except SubmitError, exc:
            submit_failures.append((project, revision, exc))
            print 'Error:', exc
        except SubmitRequestError, exc:
            submit_request_failures.append((project, revision, exc))
            print 'Error:', exc
        finally:
            if upstream_git_dir and os.path.exists(upstream_git_dir):
                shutil.rmtree(upstream_git_dir)
            if private_git_dir and os.path.exists(private_git_dir):
                shutil.rmtree(private_git_dir)

    if request_projects:
        print '\n-----[ Request Mixed Git Repository List]----------'
        print '\n'.join(str(item) for item in request_projects)

    if new_private_projects:
        print '\n-----[Does not exist Private Git Repository List]----------'
        print '\n'.join(new_private_projects)

    if overwrite_alreadys:
        print '\n----[ Already Overwrite into %s Branch List]-----------'%(branch)
        print '\n'.join(str(item) for item in overwrite_alreadys)

    if overwrite_successs:
        print '\n----[Overwrite into %s Branch Success List]-----------'%(branch)
        print '\n'.join(str(item) for item in overwrite_successs)

    if upstream_failures:
        print '\n-----[Update Upstream %s Branch Failure List]----------------'%(UPSTREAM_BRANCH)
        print '\n'.join(str(item) for item in upstream_failures)

    if overwrite_failures:
        print '\n-----[Overwrite into %s Branch Failure List]----------'%(branch)
        print '\n'.join(str(item) for item in overwrite_failures)

    if merge_failures:
        print '\n-----[Merge Failure List]---------------------------------'
        print '\n'.join(str(item) for item in merge_failures)

    if merge_successs:
        print '\n-----[Merge Success List]---------------------------------'
        print '\n'.join(str(item) for item in merge_successs)

    if submit_failures:
        print '\n-----[Submit Failure List]--------------------------------'
        print '\n'.join(str(item) for item in submit_failures)

    if submit_request_successs:
        print '\n-----[SR(Submit Request) Success List]---------------------------------'
        print '\n'.join(str(item) for item in submit_request_successs)

    if submit_request_failures:
        print '\n-----[SR(Submit Request) Failure List]--------------------------------'
        print '\n'.join(str(item) for item in submit_request_failures)

    #### Send mail to maintainers
    if True:
        ### Init MailSender ####
        my_mail = MailSender()
        if upstream_failures or \
           overwrite_failures or \
           merge_failures or \
           submit_failures or \
           submit_request_failures:
            my_mail.add_title('[Auto Git-Sync Fail] Target Project:%s Branch:%s' %(target_project, branch))
        else:
            my_mail.add_title('[Auto Git-Sync] Target Project:%s Branch:%s' %(target_project, branch))
        my_mail.add_message('Hello\n A Build System inform you about Auto Git-Sync results\n\n')
        my_mail.add_message('Public Source Snapshot: %s \nUpstream Branch: %s\nTarget Project: %s\nTarget Branch: %s' \
                            %(request_snapshot, UPSTREAM_BRANCH, target_project, branch))

        print 'members:',email_to_members
        # Add maintatiners to mail
        if email_to_submitters:
            my_mail.add_receiver(email_to_submitters)
        #[my_mail.add_maintainers(private_gerrit, group_name = name) for name in email_to_members]

        if new_private_projects:
            my_mail.add_message('\n-----[Does not exist Private Git Repository List]-----------')
            [my_mail.add_message(str(item)) for item in new_private_projects]

        if overwrite_alreadys:
            my_mail.add_message('\n----[Already Overwrite into %s Branch List]-----------'%(branch))
            [my_mail.add_message(str(item)) for item in overwrite_alreadys]

        if overwrite_successs:
            my_mail.add_message('\n-----[Overwrite into %s Branch Success List]-----------'%(branch))
            [my_mail.add_message(str(item)) for item in overwrite_successs]

        if upstream_failures:
            my_mail.add_message('\n-----[Update Upstream %s Branch Failure List]-----------'%(UPSTREAM_BRANCH))
            [my_mail.add_message(str(item)) for item in upstream_failures]

        if overwrite_failures:
            my_mail.add_message('\n-----[Overwrite into %s Branch Failure List]----------'%(branch))
            [my_mail.add_message(str(item)) for item in overwrite_failures]

        if merge_failures:
            my_mail.add_message('\n-----[Merge Failure List]---------------------------------')
            [my_mail.add_message(str(item)) for item in merge_failures]

        if merge_successs:
            my_mail.add_message('\n-----[Merge Success List]---------------------------------')
            [my_mail.add_message(str(item)) for item in merge_successs]

        if submit_failures:
            my_mail.add_message('\n-----[Submit Failure List]--------------------------------')
            [my_mail.add_message(str(item)) for item in submit_failures]

        if submit_request_successs:
            my_mail.add_message('\n-----[SR(Submit Request) Success List]--------------------------------')
            [my_mail.add_message(str(item)) for item in submit_request_successs]


        if submit_request_failures:
            my_mail.add_message('\n-----[SR(Submit Request) Failure List]--------------------------------')
            [my_mail.add_message(str(item)) for item in submit_request_failures]

        if pure_git_sr_list:
            my_mail.add_message('\n-----[Merge Skipped Pure Git SR List]--------------------------------')
            [my_mail.add_message(str(item)) for item in pure_git_sr_list]

        if new_private_projects or overwrite_alreadys or overwrite_successs or \
            upstream_failures or overwrite_failures or merge_failures or submit_failures or merge_successs or \
            submit_request_successs or submit_request_failures:
            # Send mail to maintainers
            my_mail.send_mail()

def main():
    """
    Script entry point.
    """

    print '---[JOB STARTED]-------------------------'

    if os.getenv('TRIGGER_INFO', None):
        content = trigger_info(os.getenv('TRIGGER_INFO'))
    else:
        return

    if not os.getenv('SR_SYNC_PROJECTS'):
        print 'No SR_SYNC_PROJECTS'
        return

    enabled_projects = ast.literal_eval(os.getenv('SR_SYNC_PROJECTS'))
    
    private_gerrit = gerrit.Gerrit(os.getenv('GERRIT_HOSTNAME'),
                           os.getenv('GERRIT_USERNAME'),
                           os.getenv('GERRIT_SSHPORT'),
                           int(os.getenv('GERRIT_SILENT_MODE')))

    #print enabled_projects
    for target in enabled_projects[content.get('obs_target_prj')]:
        if target.get('git-sync') == 'Yes' and target.get('target') == content.get('obs_dest_prj'):
            tmpdir = tempfile.mkdtemp(prefix=os.getenv('WORKSPACE')+'/')
            update_sync_for_git_sync(tmpdir, private_gerrit, target, content['requests'][0], content['snapshot'], content.get('sync_user'))
        else:
            print 'obs_dest_prj: %s project : git-sync option is not enabled..' %(target.get('target'))

if __name__ == '__main__':
    sys.exit(main())


