#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.md', which is part of this source code package.
#

import time

from kubernetes_py.K8sContainer import K8sContainer
from kubernetes_py.K8sExceptions import CronJobAlreadyRunningException, CronJobRunException, TimedOutException
from kubernetes_py.K8sObject import K8sObject
from kubernetes_py.K8sPod import K8sPod
from kubernetes_py.K8sVolume import K8sVolume
from kubernetes_py.models.v1beta1.CronJob import CronJob
from kubernetes_py.utils import is_valid_list, is_reachable, sanitize_version_number


class K8sCronJob(K8sObject):

    POD_RUN_WAIT_TIMEOUT_SECONDS = 600

    def __init__(self, config=None, name=None):

        temp = K8sObject(config=config, obj_type='Pod', name='temp')
        _type = 'CronJob'

        if config and is_reachable(config):
            v = temp.server_version()
            if sanitize_version_number(v['major']) == 1 and sanitize_version_number(v['minor']) == 4:
                _type = 'ScheduledJob'

        super(K8sCronJob, self).__init__(
            config=config,
            obj_type=_type,
            name=name
        )

        if config is not None and config.pull_secret is not None:
            self.add_image_pull_secrets(config.pull_secret)

    # -------------------------------------------------------------------------------------  override

    def create(self):
        super(K8sCronJob, self).create()
        self.get()
        return self

    def update(self):
        super(K8sCronJob, self).update()
        self.get()
        return self

    def list(self, pattern=None, labels=None):
        ls = super(K8sCronJob, self).list(labels=labels)
        jobs = list(map(lambda x: CronJob(x), ls))
        if pattern is not None:
            jobs = list(filter(lambda x: pattern in x.name, jobs))
        k8s = []
        for x in jobs:
            j = K8sCronJob(config=self.config, name=x.name).from_model(m=x)
            k8s.append(j)
        return k8s

    # -------------------------------------------------------------------------------------  get

    def get(self):
        self.model = CronJob(self.get_model())
        return self

    # -------------------------------------------------------------------------------------  add

    def add_container(self, container=None):
        if not isinstance(container, K8sContainer):
            raise SyntaxError(
                'K8sCronJob.add_container() container: [ {0} ] is invalid.'.format(container))

        containers = self.model.spec.job_template.spec.template.spec.containers
        if containers is None:
            containers = []
        filtered = list(filter(lambda x: x.name != container.name, containers))
        filtered.append(container.model)
        self.model.spec.job_template.spec.template.spec.containers = filtered
        return self

    def add_image_pull_secrets(self, secrets=None):
        self.model.spec.job_template.spec.template.spec.add_image_pull_secrets(secrets)
        return self

    def add_pod_label(self, k=None, v=None):
        labels = self.model.spec.job_template.spec.template.metadata.labels
        if labels is None:
            labels = {}
        labels.update({k: v})
        self.model.spec.job_template.spec.template.metadata.labels = labels
        return self

    def add_volume(self, volume=None):
        if not isinstance(volume, K8sVolume):
            raise SyntaxError(
                'K8sCronJob.add_volume() volume: [ {0} ] is invalid.'.format(volume))

        volumes = self.model.spec.job_template.spec.template.spec.volumes
        if volume.model not in volumes:
            volumes.append(volume.model)
        self.model.spec.job_template.spec.template.spec.volumes = volumes
        return self

    # -------------------------------------------------------------------------------------  del

    def del_pod_label(self, k=None):
        labels = self.model.spec.job_template.spec.template.metadata.labels
        if k in labels:
            labels.pop(k)
            self.model.spec.job_template.spec.template.metadata.labels = labels
        return self

    # -------------------------------------------------------------------------------------  schedule

    @property
    def schedule(self):
        return self.model.spec.schedule

    @schedule.setter
    def schedule(self, s=None):
        self.model.spec.schedule = s

    # -------------------------------------------------------------------------------------  startingDeadlineSeconds

    @property
    def starting_deadline_seconds(self):
        return self.model.spec.starting_deadline_seconds

    @starting_deadline_seconds.setter
    def starting_deadline_seconds(self, s=None):
        self.model.spec.starting_deadline_seconds = s

    # -------------------------------------------------------------------------------------  activeDeadlineSeconds

    @property
    def active_deadline_seconds(self):
        return self.model.spec.job_template.spec.active_deadline_seconds

    @active_deadline_seconds.setter
    def active_deadline_seconds(self, s=None):
        self.model.spec.job_template.spec.active_deadline_seconds = s

    # -------------------------------------------------------------------------------------  concurrencyPolicy

    @property
    def concurrency_policy(self):
        return self.model.spec.concurrency_policy

    @concurrency_policy.setter
    def concurrency_policy(self, cp=None):
        self.model.spec.concurrency_policy = cp

    # -------------------------------------------------------------------------------------  suspend

    @property
    def suspend(self):
        return self.model.spec.suspend

    @suspend.setter
    def suspend(self, s=None):
        self.model.spec.suspend = s

    # -------------------------------------------------------------------------------------  parallelism

    @property
    def parallelism(self):
        return self.model.spec.job_template.spec.parallelism

    @parallelism.setter
    def parallelism(self, jp=None):
        self.model.spec.job_template.spec.parallelism = jp

    # -------------------------------------------------------------------------------------  completions

    @property
    def completions(self):
        return self.model.spec.job_template.spec.completions

    @completions.setter
    def completions(self, c=None):
        self.model.spec.job_template.spec.completions = c

    # -------------------------------------------------------------------------------------  containers

    @property
    def containers(self):
        objs = []
        for c in self.model.spec.job_template.spec.template.spec.containers:
            k8scontainer = K8sContainer(name=c.name, image=c.image)
            k8scontainer.model = c
            objs.append(k8scontainer)
        return objs

    @containers.setter
    def containers(self, containers=None):
        if not is_valid_list(containers, K8sContainer):
            raise SyntaxError(
                'K8sCronJob: containers: [ {} ] is invalid.'.format(containers))

        models = []
        for obj in containers:
            models.append(obj.model)
        self.model.spec.job_template.spec.template.spec.containers = models

    # -------------------------------------------------------------------------------------  container_image

    @property
    def container_image(self):
        data = {}
        for c in self.containers:
            data[c.name] = c.image
        return data

    @container_image.setter
    def container_image(self, tup=None):
        if not isinstance(tup, tuple):
            raise SyntaxError(
                'K8sCronJob.container_image() must be a tuple of the form (name, image)')

        name, image = tup
        found = list(filter(lambda x: x.name == name, self.containers))
        if found:
            new = list(filter(lambda x: x.name != name, self.containers))
            found[0].image = image
            new.append(found[0])
            self.containers = new

    # -------------------------------------------------------------------------------------  dnsPolicy

    @property
    def dns_policy(self):
        return self.model.spec.template.spec.dns_policy

    @dns_policy.setter
    def dns_policy(self, policy=None):
        if policy not in self.model.spec.job_template.spec.template.spec.VALID_DNS_POLICIES:
            raise SyntaxError(
                'K8sJob: dns_policy: [ {} ] is invalid, expected [ {} ].'.format(
                    policy, self.model.spec.job_template.spec.template.spec.VALID_DNS_POLICIES))

        self.model.spec.job_template.spec.template.spec.dns_policy = policy

    # -------------------------------------------------------------------------------------  restartPolicy

    @property
    def restart_policy(self):
        return self.model.spec.job_template.spec.template.spec.restart_policy

    @restart_policy.setter
    def restart_policy(self, p=None):
        self.model.spec.job_template.spec.template.spec.restart_policy = p

    # -------------------------------------------------------------------------------------  lastScheduleTime

    @property
    def last_schedule_time(self):
        return self.model.status.last_schedule_time

    @last_schedule_time.setter
    def last_schedule_time(self, t=None):
        raise NotImplementedError(
            'K8sCronJob: last_schedule_time is read-only.')

    # -------------------------------------------------------------------------------------  active

    @property
    def active(self):
        return self.status.active

    @active.setter
    def active(self, a=None):
        raise NotImplementedError(
            "K8sCronJob: active is read-only.")

    # -------------------------------------------------------------------------------------  pod

    @property
    def pod(self):
        podspec = self.model.spec.job_template.spec.template.spec
        name = "{}-{}".format(self.name, int(time.time()))
        pod = K8sPod(config=self.config, name=name)
        pod.model.spec = podspec
        return pod

    @pod.setter
    def pod(self, p=None):
        raise NotImplementedError(
            'K8sCronjob: pod is read-only.')

    # -------------------------------------------------------------------------------------  pod labels

    @property
    def pod_labels(self):
        return self.model.spec.job_template.spec.template.metadata.labels

    @pod_labels.setter
    def pod_labels(self, labels=None):
        self.model.spec.job_template.spec.template.metadata.labels = labels


    # -------------------------------------------------------------------------------------  image pull secrets

    @property
    def image_pull_secrets(self):
        return self.model.spec.job_template.spec.template.spec.image_pull_secrets

    @image_pull_secrets.setter
    def image_pull_secrets(self, secrets=None):
        self.model.spec.job_template.spec.template.spec.image_pull_secrets = secrets

    # -------------------------------------------------------------------------------------  run

    def run(self, timeout=POD_RUN_WAIT_TIMEOUT_SECONDS):
        """
        Forces a K8sCronJob to run immediately.

        - Fail if the K8sCronJob is currently running on-schedule.
        - Suspend the K8sCronJob.
        - Spawn a K8sPod.
        - Unsuspend a K8sCronJob.

        :param timeout: The timeout, in seconds, after which to kill the K8sPod.

        :return: None.
        """

        if not isinstance(timeout, int):
            raise SyntaxError("K8sCronJob.run() timeout: [ {} ] is invalid.")

        if len(self.active):
            raise CronJobAlreadyRunningException(
                "K8sCronJob.run() failed: CronJob: [ {} ] "
                "has [ {} ] active Jobs currently.".format(self.name, len(self.active)))

        self.suspend = True
        self.update()

        pod = self.pod
        if timeout:
            self.POD_RUN_WAIT_TIMEOUT_SECONDS = timeout

        try:
            pod.create()
            start_time = time.time()
            while pod.phase not in ['Succeeded', 'Failed']:
                pod.get()
                time.sleep(2)
                self._check_timeout(start_time)

        except Exception as err:
            raise CronJobRunException("K8sCronJob.run() failed: {}".format(err))

        finally:
            pod.delete()
            self.suspend = False
            self.update()

    def _check_timeout(self, start_time=None):
        if self.POD_RUN_WAIT_TIMEOUT_SECONDS == -1:
            return
        elapsed_time = time.time() - start_time
        if elapsed_time >= self.POD_RUN_WAIT_TIMEOUT_SECONDS:  # timeout
            raise TimedOutException(
                "Timed out running one-off CronJob: [ {} ]".format(self.name))
