import { Config } from './Config'
import { DeploymentParam } from './DeploymentParam'
//@ts-ignore
import * as k8s from '@kubernetes/client-node'
import { OpResult, logger } from 'mslib'
import { PVCParam } from './PVCParam'
import { ServiceParam } from './ServiceParam'

/**
 * K8S操作API
 */
export class ApiClient {

    private appApi: k8s.AppsV1Api
    private coreApi: k8s.CoreV1Api
    readonly config: Config
    readonly id: string
    readonly name: string

    constructor(config: Config) {
        this.config = config
        this.id = this.config.id
        this.name = this.config.name
        this.makeApiClient()
    }

    private makeApiClient() {
        const kc = new k8s.KubeConfig();
        const cluster = {
            name: this.config.name,
            server: this.config.apiUrl,
            caData: this.config.ca
        };

        const user = {
            certData: this.config.cc,
            keyData: this.config.ck,
        };

        const context = {
            name: 'my-context',
            cluster: cluster.name,
        };

        kc.loadFromOptions({
            clusters: [cluster],
            users: [user],
            contexts: [context],
            currentContext: context.name,
        });
        this.appApi = kc.makeApiClient(k8s.AppsV1Api);
        this.coreApi = kc.makeApiClient(k8s.CoreV1Api);
    }

    /**
    * 获取Deplyment
    * @param namespace 命名空间
    * @param name deplyment名称
    */
    async getDeplyment(namespaceName: string, name: string): Promise<OpResult<any>> {
        try {
            const response = await this.appApi.readNamespacedDeployment(name, namespaceName)
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] getDeplyment error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }

    }

    /**
     * 创建一个新的部署
     * @param namespace 
     * @param params 
     */
    async createDeployment(namespace: string, params: DeploymentParam): Promise<OpResult<any>> {

        const deploymentSpec = new k8s.V1DeploymentSpec()
        deploymentSpec.replicas = params.replicas || 1

        const selector = new k8s.V1LabelSelector()
        selector.matchLabels = {
            'app': params.deployId
        }
        deploymentSpec.selector = selector

        //template
        const template = new k8s.V1PodTemplateSpec()
        template.metadata = {
            labels: selector.matchLabels
        }
        template.spec = this.createV1PodSpec(params)
        deploymentSpec.template = template
        //template

        const deplyment = new k8s.V1Deployment()
        deplyment.metadata = this.createMetadata(namespace, params)
        deplyment.spec = deploymentSpec

        try {
            logger.debug('[K8S-API] createDeployment begin ==========>' + JSON.stringify(deplyment))
            const response = await this.appApi.createNamespacedDeployment(namespace, deplyment)
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] createDeployment error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }



    /**
     * 删除部署
     * @param namespace 
     * @param name 部署名称 
     */
    async deleteDeployment(namespace: string, name: string): Promise<OpResult<any>> {
        const op = await this.getDeplyment(namespace, name)
        if (!op.isSuccess()) {
            return op
        }
        const deployment = op.data
        deployment.spec.replicas = 0
        try {
            await this.appApi.replaceNamespacedDeployment(name, namespace, deployment);
            const response = await this.appApi.deleteNamespacedDeployment(name, namespace)
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] deleteDeployment error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
     * 获取指定 POD
     * @param namespace 
     */
    async getPod(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            const response = await this.coreApi.readNamespacedPod(name, namespace)
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] getPod error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
     * 获取POD 列表
     * @param namespace 
     */
    async getPodList(namespace: string): Promise<OpResult<any>> {
        try {
            const response = await this.coreApi.listNamespacedPod(namespace)
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] getPodList error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
   * 创建一个新的POD
   * @param namespace 
   * @param params 
   */
    async createPod(namespace: string, params: DeploymentParam, forceCreate: boolean = false): Promise<OpResult<any>> {
        const pod = new k8s.V1Pod()
        pod.metadata = this.createMetadata(namespace, params)
        pod.spec = this.createV1PodSpec(params)

        try {
            logger.debug('[K8S-API] createPOD begin ==========>' + JSON.stringify(pod))
            if (forceCreate) {
                await this.deletePod(namespace, params.appName)
            }
            const response = await this.coreApi.createNamespacedPod(namespace, pod)
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] createPod error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }

    }

    /**
  * 创建一个新的POD
  * @param namespace 
  * @param params 
  */
    async createPodAndPvc(namespace: string, params: DeploymentParam, pvc: PVCParam): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] createPodAndPvc begin ==========>', params)
            if (pvc) {
                params.pvc.name = pvc.name
                let op = await this.createPVC(namespace, pvc, false)
                if (!op.isSuccess()) {
                    return op
                }
                let sleepSeconds = 1
                while (sleepSeconds < 30) {
                    await this.sleep(1000)
                    sleepSeconds++
                    op = await this.getPVC(namespace, pvc.name)
                    if (op.isSuccess() && op.data.status.phase === 'Bound') {
                        op = await this.createPod(namespace, params, false)
                        if (!op.isSuccess()) {
                            await this.deletePVC(namespace, pvc.name)
                        }
                        return op;
                    }
                }
            }
            return OpResult.error('创建PVC超时......')
        } catch (e) {
            logger.error('[K8S-API] createPodAndPvc error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
     * 删除POD
     * @param namespace 
     * @param name POD名称 
     */
    async deletePod(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] deletePod begin ==========>namespace:%s,name:%s', namespace, name)
            const response = await this.coreApi.deleteNamespacedPod(name, namespace);
            logger.debug('[K8S-API] deletePod end ==========>namespace:%s,name:%s' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] deletePod error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
     * 获取指定 PVC
     * @param namespace 
     */
    async getPVC(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] getPVC begin ==========>namespace:%s,name:%s', namespace, name)
            const response = await this.coreApi.readNamespacedPersistentVolumeClaim(name, namespace)
            logger.debug('[K8S-API] getPVC end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] getPVC error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
       * 创建一个新的 PVC
       * @param namespace
       * @param params
       */
    async createPVC(namespace: string, params: PVCParam, forceCreate: boolean = false): Promise<OpResult<any>> {
        const pvc = new k8s.V1PersistentVolumeClaim()
        pvc.metadata = {
            name: params.name
        }
        pvc.spec = new k8s.V1PersistentVolumeClaimSpec()
        pvc.spec.accessModes = [params.accessModes || 'ReadWriteOnce']
        let storage = params.storage
        if (!storage.endsWith('Gi')) {
            storage = params.storage + 'Gi'
        }
        pvc.spec.resources = {
            requests: {
                storage: storage
            }
        }
        pvc.spec.storageClassName = params.storageClassName
        try {
            logger.debug('[K8S-API] createPVC begin ==========>' + JSON.stringify(pvc))
            if (forceCreate) {
                await this.deletePVC(namespace, params.name)
            }
            const response = await this.coreApi.createNamespacedPersistentVolumeClaim(namespace, pvc)
            logger.debug('[K8S-API] createPVC end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] createPVC error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }

    }


    /**
     * 删除PVC
     * @param namespace 
     * @param name PVC名称 
     */
    async deletePVC(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] deletePVC begin ==========>namespace:%s,name:%s', namespace, name)
            const response = await this.coreApi.deleteNamespacedPersistentVolumeClaim(name, namespace);
            logger.debug('[K8S-API] deletePVC end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] deletePod error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }


    /**
     * 创建服务
     * @param namespace 
     * @param param
     */
    async createService(namespace: string, param: ServiceParam, forceCreate: boolean = false): Promise<OpResult<any>> {
        const metadata = new k8s.V1ObjectMeta()
        metadata.namespace = namespace
        metadata.name = param.name

        if (this.config.cloudProvider === 'ALIYUN') {
            metadata.annotations = this.createAliyunSLBMetadataAnnotations(param)
        }

        const service = new k8s.V1Service()
        service.metadata = metadata
        service.spec = new k8s.V1ServiceSpec()
        service.spec.selector = {
            "app": param.podLabel
        }
        service.spec.externalTrafficPolicy = 'Cluster'
        service.spec.type = param.type || 'LoadBalancer'
        service.spec.ports = []
        for (let port of param.ports) {
            const servicePort = new k8s.V1ServicePort()
            servicePort.port = port.nodePort
            servicePort.targetPort = port.targetPost as any
            servicePort.protocol = port.protocol
            servicePort.name = 'p' + port.targetPost
            service.spec.ports.push(servicePort)
        }
        try {
            logger.debug('[K8S-API] createService begin ==========>' + JSON.stringify(service))
            if (forceCreate) {
                await this.deleteService(namespace, param.name)
            }
            const response = await this.coreApi.createNamespacedService(namespace, service);
            logger.debug('[K8S-API] createService end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] createService error ==========>', e)
            return this.errorToOpResult(e)
        }

    }

    /**
     * 删除服务
     * @param namespace 
     * @param name 服务名称
     */
    async deleteService(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] deleteService begin ==========>namespace:%s,name:%s', namespace, name)
            const response = await this.coreApi.deleteNamespacedService(name, namespace);
            logger.debug('[K8S-API] deleteService end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] deleteService error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
     * 获取服务信息
     * @param namespace 
     * @param name 服务名称
     */
    async getService(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] getService begin ==========>namespace:%s,name:%s', namespace, name)
            const response = await this.coreApi.readNamespacedService(name, namespace);
            logger.debug('[K8S-API] getService end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] getService error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }

    /**
   * 获取服务信息列表
   * @param namespace 
   * @param name 服务名称
   */
    async getServiceList(namespace: string, name: string): Promise<OpResult<any>> {
        try {
            logger.debug('[K8S-API] getServiceList begin ==========>namespace:%s,name:%s', namespace, name)
            const response = await this.coreApi.listNamespacedService(name, namespace);
            logger.debug('[K8S-API] getServiceList end ==========>' + JSON.stringify(response))
            return OpResult.success('OK', response.body)
        } catch (e) {
            logger.error('[K8S-API] getServiceList error ==========>' + JSON.stringify(e))
            return this.errorToOpResult(e)
        }
    }


    /**
     * 阿里云SLB注解
     * @param param 
     */
    private createAliyunSLBMetadataAnnotations(param: ServiceParam): any {
        const result = {}
        if (param.slbId) {
            result['service.beta.kubernetes.io/alicloud-loadbalancer-id'] = param.slbId
        }

        if (param.networkType === 'intranet') {//内网
            result['service.beta.kubernetes.io/alicloud-loadbalancer-address-type'] = 'intranet'
        } else {//外网
            //slb规格默认简约型
            result['service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec'] = 'slb.s1.small'
            const has443 = param.ports.find(it => it.targetPost === 443)
            const has80 = param.ports.find(it => it.targetPost === 80)
            let slbports = has443 ? ['https:443'] : []
            if (has80) {
                slbports.push('http:80')
            }
            if (slbports.length > 0) {
                result['service.beta.kubernetes.io/alibaba-cloud-loadbalancer-protocol-port'] = slbports.join(',')
            }
        }
        return result
    }



    private createMetadata(namespace: string, params: DeploymentParam): k8s.V1ObjectMeta {
        const metadata = new k8s.V1ObjectMeta()
        metadata.labels = params.labels || {}
        metadata.labels['app'] = params.deployId
        metadata.namespace = namespace
        metadata.name = params.appName
        return metadata
    }

    private createV1PodSpec(params: DeploymentParam): k8s.V1PodSpec {
        const spec = new k8s.V1PodSpec()

        const container = new k8s.V1Container()
        container.name = params.appName
        if (params.command) {
            container.command = [params.command]
        }
        if (params.envs) {
            const envs: Array<k8s.V1EnvVar> = []
            const keys = Object.keys(params.envs)
            keys.forEach(key => {
                const env = new k8s.V1EnvVar()
                env.name = key
                env.value = params.envs[key]
                envs.push(env)
            })
            container.env = envs
        }
        container.image = params.image

        if (params.ports) {
            const ports: Array<k8s.V1ContainerPort> = []
            container.ports = ports
            for (let row of params.ports) {
                ports.push({
                    name: 'p' + row.port,
                    containerPort: parseInt(row.port.toString()),
                    protocol: row.protocol
                })
            }
            container.ports = ports
        }
        let memory = params.memory + 'Gi'
        if (parseFloat(memory) < 1) {//小于1G
            let value = parseInt((parseFloat(memory) * 1024) + '');
            if (value < 128) {
                value = 128
            } else if (value > 128 && value < 256) {
                value = 256
            }
            memory = value + 'Mi'
        }
        container.resources = {
            limits: {
                cpu: params.cpu,
                memory: memory
            },
            requests: {
                cpu: params.cpu,
                memory: memory
            }
        }
        spec.containers = [container]
        if (params.imagePullSecret) {
            spec.imagePullSecrets = [{
                name: params.imagePullSecret
            }]
        }

        if (params.pvc) {

            container.volumeMounts = [
                {
                    name: params.pvc.name,
                    mountPath: params.pvc.mountPath
                }
            ]
            spec.volumes = [
                {
                    name: params.pvc.name,
                    persistentVolumeClaim: {
                        claimName: params.pvc.name
                    }
                }
            ]
        }
        return spec;
    }


    private errorToOpResult(error): OpResult<any> {
        let message = error.message
        if (error.response && error.response.body) {
            message = error.response.body.status + ' : ' + error.response.body.message
        }
        return OpResult.error(message)
    }

    private sleep(ms: number) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

}
