import { Input, Component, Inject, OnDestroy } from 'ng-metadata/core';
import { AngularPollChannel, PollHandle } from "../../services/services.types";
const templateUrl = require('./kube-deploy-modal.component.html');
const styleUrl = require('./kube-deploy-modal.css');

// The response from the API about deployment rollout status
type DeploymentRollout = {
    status: 'available' | 'progressing' | 'failed',
    message: string
};

type DeploymentStatus = {
    name: string,
    numPods: number,
    message?: string,
    pollHandler?: PollHandle,
}

const DEPLOYMENT_POLL_SLEEPTIME = 5000; /* 5 seconds */

@Component({
    selector: 'kube-deploy-modal',
    templateUrl,
    styleUrls: [ styleUrl ],
})
export class KubeDeployModalComponent implements OnDestroy {
    @Input('<') public loadedConfig;
    private state
        : 'loadingDeployments'
        | 'readyToDeploy'
        | 'deployingConfiguration'
        | 'cyclingDeployments'
        | 'deployed'
        | 'error'
        | 'rolledBackWarning' = 'loadingDeployments';
    private errorMessage: string;
    private deploymentsStatus: DeploymentStatus[] = [];
    private deploymentsCycled: number = 0;
    private onDestroyListeners: Function[] = [];
    private rollingBackStatus
        : 'none'
        | 'offer'
        | 'rolling' = 'none';

    constructor(@Inject('ApiService') private ApiService, @Inject('AngularPollChannel') private AngularPollChannel: AngularPollChannel) {
        ApiService.scGetNumDeployments().then(resp => {
            this.deploymentsStatus = resp.items.map(dep => ({ name: dep.metadata.name, numPods: dep.spec.replicas }));
            this.state = 'readyToDeploy';
        }).catch(err => {
            this.state = 'error';
            this.errorMessage = `There are no Quay deployments active in this namespace. \
                                Please check that you are running this \
                                tool in the same namespace as the Quay Enterprise application\
                                Associated error message: ${err.toString()}`;
        })
    }

    // Call all listeners of the onDestroy
    ngOnDestroy(): any {
        this.onDestroyListeners.forEach(fn => {
            fn()
        });
    }


    deployConfiguration(): void {
        this.ApiService.scDeployConfiguration().then(() => {
            this.state = 'deployingConfiguration';
            const deploymentNames: string[] = this.deploymentsStatus.map(dep => dep.name);

            this.ApiService.scCycleQEDeployments({ deploymentNames }).then(() => {
                this.state = 'cyclingDeployments';
                this.watchDeployments();
            }).catch(err => {
                this.state = 'error';
                this.errorMessage = `Could not cycle the deployments with the new configuration. Error: ${err.toString()}`;
            })
        }).catch(err => {
            this.state = 'error';
            this.errorMessage = `Could not deploy the configuration. Error: ${err.toString()}`;
        })
    }

    watchDeployments(): void {
        this.deploymentsStatus.forEach(deployment => {
            const pollChannel = this.AngularPollChannel.create({
                // Have to mock the scope object for the poll channel since we're calling into angular1 code
                // We register the onDestroy function to be called later when this object is destroyed
                '$on': (_, onDestruction) => { this.onDestroyListeners.push(onDestruction) }
            }, this.getDeploymentStatus(deployment), DEPLOYMENT_POLL_SLEEPTIME);

            pollChannel.start();
        });
    }

    // Query each deployment every 5s, and stop polling once it's either available or failed
    getDeploymentStatus(deployment: DeploymentStatus): (boolean) => void {
        return (continue_callback: (shouldContinue: boolean) => void) => {
            const params = {
                'deployment': deployment.name
            };

            this.ApiService.scGetDeploymentRolloutStatus(null, params).then((deploymentRollout: DeploymentRollout) => {
                if (deploymentRollout.status === 'available') {
                    continue_callback(false);

                    this.deploymentsCycled++;
                    if (this.deploymentsCycled === this.deploymentsStatus.length) {
                        this.state = 'deployed';
                    }
                } else if (deploymentRollout.status === 'progressing') {
                    continue_callback(true);
                    deployment.message = deploymentRollout.message;
                } else { // deployment rollout failed
                    this.state = 'error';
                    continue_callback(false);
                    deployment.message = deploymentRollout.message;
                    this.errorMessage = `Could not cycle deployments: ${deploymentRollout.message}`;

                    // Only offer rollback if we loaded/populated a config. (Can't rollback an initial setup)
                    if (this.loadedConfig) {
                        this.rollingBackStatus = 'offer';
                        this.errorMessage = `Could not cycle deployments: ${deploymentRollout.message}`;
                    }
                }
            }).catch(err => {
                continue_callback(false);
                this.state = 'error';
                this.errorMessage = `Could not cycle the deployments with the new configuration. Error: ${err.toString()}\
                                     Would you like to rollback the deployment to its previous state?`;
                // Only offer rollback if we loaded/populated a config. (Can't rollback an initial setup)
                if (this.loadedConfig) {
                    this.rollingBackStatus = 'offer';
                    this.errorMessage = `Could not get deployment information for: ${deployment}`;
                }
            });
        }
    }

    rollbackDeployments(): void {
        this.rollingBackStatus = 'rolling';
        const deploymentNames: string[] = this.deploymentsStatus.map(dep => dep.name);

        this.ApiService.scRollbackDeployments({ deploymentNames }).then(() => {
            this.state = 'rolledBackWarning';
            this.rollingBackStatus = 'none';
        }).catch(err => {
            this.rollingBackStatus = 'none';
            this.state = 'error';
            this.errorMessage = `Could not cycle the deployments back to their previous states. Please contact support: ${err.toString()}`;
        })
    }
}