#!/usr/bin/env python3

import _add_root_dir_to_path

from ci._types import *
from ci._config import *
from ci._qloud import *
from lib.aux import *
from lib.jenkins import *
from lib.local_storage import *

import yaml
import requests

import argparse
import json
import os.path
import sys
import subprocess
import datetime

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--list", help='list all enqueued tasks', action="store_true")
    args = parser.parse_args()
    return args


def main():
    args = parse_args()

    config = load_config('global.yml')
    apps = load_app_config('app.yml')
    jenkins = Jenkins(config.jenkins, apps)
    registry = DockerRegistry(config.registry, apps)
    qloud = Qloud(config.qloud, apps, registry)
    storage = LocalStorage(config.storage)
    task_storage = TasksStorage(config.storage)

    while True:
        queue = None
        canceled_tasks = task_storage.get_canceled()
        paused_tasks = task_storage.get_paused()
        queue = task_storage.get_queue()
        finished = task_storage.get_finished()

        if args.list:
            print_task_queue(queue)
            return

        if not queue or len(queue) == 0:
            stdout('no tasks')
            time.sleep(5)
            continue

        if not finished:
            finished = list()

        # process the tasks queue
        for i in range(0, len(queue)):
            task = Task.load(queue[i])

            if canceled_tasks and task.id in canceled_tasks:
                stdout('canceled', task.id, task.app)
                task = task.update(status = TaskStatus.canceled)
                queue[i] = task

            if paused_tasks and task.id in paused_tasks:
                stdout('paused', task.id, task.app)
                continue

            if task.status == TaskStatus.nobuild:
                build = jenkins.get_build_info(task.app, task.build_number)

                if build and not build.finished:
                    stdout('building...', task.id, task.build_number, task.build_attempt)
                    continue

                if build and build.success:
                    stdout('build finished for', task.id)
                    task = task.update(
                        status = TaskStatus.active,
                        new_docker_image = build.docker_image,
                        new_docker_image_sha256 = registry.get_sha256(task.app, build.docker_image.split(':')[1]), # TODO check errors
                        build_ts = time.time()
                    )
                    queue[i] = task

                if build and not build.success:
                    stdout('build failed for', task.id)
                    if task.build_attempt >= config.max_build_attempts:
                        task = task.update(status = TaskStatus.failed, build_attempt = task.build_attempt+1)
                        queue[i] = task
                        continue
                    else:
                        task = task.update(build_attempt = task.build_attempt+1)
                        queue[i] = task

                # If task still need to be built - just do it.
                if task.status == TaskStatus.nobuild:
                    stdout('start build for', task.id)
                    build_number = jenkins.start_build(task.app, task.branch)
                    task = task.update(build_number = build_number)
                    queue[i] = task
                    continue

            if task.status == TaskStatus.active:
                if task.next_step >= len(task.steps):
                    task = task.update(status = TaskStatus.finished)
                    queue[i] = task
                    continue

                step = task.steps[task.next_step]
                deploy = qloud.get_deploy_status(task, step)

                if deploy and not deploy.finished:
                    stdout('deploying... ', task.id, task.app, step.env, task.build_attempt, deploy.extra)
                    continue

                if deploy and deploy.finished and deploy.success:
                    stdout('deploy finished for', task.id, step.env)
                    task = task.update(next_step = task.next_step + 1)
                    queue[i] = task

                if deploy and deploy.finished and not deploy.success:
                    stdout('deploy failed for', task.id, step.app, step.env, deploy.debug)
                    if not deploy.need_retry:
                        task = task.update(status = TaskStatus.failed)
                        queue[i] = task
                        continue

                if task.next_step >= len(task.steps):
                    task = task.update(status = TaskStatus.finished)
                    queue[i] = task
                    continue

                if task.next_step > 0:
                    prev_step = task.steps[task.next_step-1]
                    if prev_step.requires_pause_after:
                        prev_step = prev_step._replace(requires_pause_after = False)
                        task.steps[task.next_step-1] = prev_step
                        paused_tasks.append(task.id)
                        continue

                if (time.time() - task.update_ts) > 3600:
                    stdout('task is automatically paused because it was inactive for more than an hour', task.id)
                    paused_tasks.append(task.id)
                    continue

                step = task.steps[task.next_step]
                res = qloud.run_deploy(task, step)
                stdout('started deploy for',
                       task.id,
                       step.app,
                       step.env,
                       config.qloud['urls']['web'] + '/mail/'
                       + step.app + '/' + step.env + "?version=" + str(res))

        # finally remove finished tasks from the queue
        try:
            for i in range(0, len(queue)):
                task = Task.load(queue[i])
                if task.status in (TaskStatus.finished, TaskStatus.failed, TaskStatus.canceled):
                    stdout('fin task:', task.id, task.app, task.revision)
                    finished.append(task)
                    queue.remove(queue[i])
                    i -= 1
                    if i >= len(queue):
                        break
        except Exception as e:
            stderr('exception while finally iterating through the tasks queue', repr(e))

        task_storage.set_queue(queue)
        task_storage.set_finished(finished)
        task_storage.set_paused(paused_tasks)
        time.sleep(5)

    return 0


def print_task_queue(queue):
    print('id', 'app', 'branch', 'status')
    for i in range(0, len(queue)):
        task = Task.load(queue[i])
        print(task.id, task.app, task.branch, TaskStatus(task.status))


if __name__ == '__main__':
    requests.packages.urllib3.disable_warnings()
    main()
