/*
 * Copyright 2019-present Ciena Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package commands

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/golang/protobuf/ptypes/empty"
	flags "github.com/jessevdk/go-flags"
	"github.com/opencord/voltctl/internal/pkg/cli/version"
	"github.com/opencord/voltctl/pkg/format"
	"github.com/opencord/voltha-protos/v5/go/voltha"
)

type VersionDetails struct {
	Version   string `json:"version"`
	GoVersion string `json:"goversion"`
	VcsRef    string `json:"gitcommit"`
	VcsDirty  string `json:"gitdirty"`
	BuildTime string `json:"buildtime"`
	Os        string `json:"os"`
	Arch      string `json:"arch"`
}

type VersionOutput struct {
	Client  VersionDetails `json:"client"`
	Cluster VersionDetails `json:"cluster"`
}

type VersionOpts struct {
	OutputOptions
	ClientOnly bool `long:"clientonly" description:"Display only client version information"`
}

var versionOpts = VersionOpts{}

var versionInfo = VersionOutput{
	Client: VersionDetails{
		Version:   version.Version,
		GoVersion: version.GoVersion,
		VcsRef:    version.VcsRef,
		VcsDirty:  version.VcsDirty,
		Os:        version.Os,
		Arch:      version.Arch,
		BuildTime: version.BuildTime,
	},
	Cluster: VersionDetails{
		Version:   "unknown-version",
		GoVersion: "unknown-goversion",
		VcsRef:    "unknown-vcsref",
		VcsDirty:  "unknown-vcsdirty",
		Os:        "unknown-os",
		Arch:      "unknown-arch",
		BuildTime: "unknown-buildtime",
	},
}

func RegisterVersionCommands(parent *flags.Parser) {
	_, err := parent.AddCommand("version", "display version", "Display client and server version", &versionOpts)
	if err != nil {
		Error.Fatalf("Unable to register version command: %s", err.Error())
	}
}

const ClientOnlyFormat = `Client:
 Version        {{.Version}}
 Go version:    {{.GoVersion}}
 Vcs reference: {{.VcsRef}}
 Vcs dirty:     {{.VcsDirty}}
 Built:         {{.BuildTime}}
 OS/Arch:       {{.Os}}/{{.Arch}}
`

const DefaultFormat = `Client:
 Version        {{.Client.Version}}
 Go version:    {{.Client.GoVersion}}
 Vcs reference: {{.Client.VcsRef}}
 Vcs dirty:     {{.Client.VcsDirty}}
 Built:         {{.Client.BuildTime}}
 OS/Arch:       {{.Client.Os}}/{{.Client.Arch}}

Cluster:
 Version        {{.Cluster.Version}}
 Go version:    {{.Cluster.GoVersion}}
 Vcs feference: {{.Cluster.VcsRef}}
 Vcs dirty:     {{.Cluster.VcsDirty}}
 Built:         {{.Cluster.BuildTime}}
 OS/Arch:       {{.Cluster.Os}}/{{.Cluster.Arch}}
`

func (options *VersionOpts) clientOnlyVersion(args []string) error {
	outputFormat := CharReplacer.Replace(options.Format)
	if outputFormat == "" {
		outputFormat = GetCommandOptionWithDefault("version", "format", ClientOnlyFormat)
	}
	if options.Quiet {
		outputFormat = "{{.Version}}"
	}

	result := CommandResult{
		Format:    format.Format(outputFormat),
		OutputAs:  toOutputType(options.OutputAs),
		NameLimit: options.NameLimit,
		Data:      versionInfo.Client,
	}

	GenerateOutput(&result)
	return nil
}

func (options *VersionOpts) Execute(args []string) error {
	if options.ClientOnly {
		return options.clientOnlyVersion(args)
	}

	conn, err := NewConnection()
	if err != nil {
		return err
	}
	defer conn.Close()

	client := voltha.NewVolthaServiceClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
	defer cancel()

	voltha, err := client.GetVoltha(ctx, &empty.Empty{})
	if err != nil {
		return err
	}

	info := make(map[string]interface{})
	err = json.Unmarshal([]byte(voltha.Version), &info)
	if err != nil {
		versionInfo.Cluster.Version = strings.ReplaceAll(voltha.Version, "\n", "")
	} else {
		var ok bool
		if _, ok = info["version"]; ok {
			versionInfo.Cluster.Version = info["version"].(string)
		} else {
			versionInfo.Cluster.Version = "unknown-version"
		}

		if _, ok = info["goversion"]; ok {
			versionInfo.Cluster.GoVersion = info["goversion"].(string)
		} else {
			versionInfo.Cluster.GoVersion = "unknown-goversion"
		}

		if _, ok = info["vcsref"]; ok {
			versionInfo.Cluster.VcsRef = info["vcsref"].(string)
		} else {
			versionInfo.Cluster.VcsRef = "unknown-vcsref"
		}

		if _, ok = info["vcsdirty"]; ok {
			versionInfo.Cluster.VcsDirty = info["vcsdirty"].(string)
		} else {
			versionInfo.Cluster.VcsDirty = "unknown-vcsdirty"
		}

		if _, ok = info["buildtime"]; ok {
			versionInfo.Cluster.BuildTime = info["buildtime"].(string)
		} else {
			versionInfo.Cluster.BuildTime = "unknown-buildtime"
		}

		if _, ok = info["os"]; ok {
			versionInfo.Cluster.Os = info["os"].(string)
		} else {
			versionInfo.Cluster.Os = "unknown-os"
		}

		if _, ok = info["arch"]; ok {
			versionInfo.Cluster.Arch = info["arch"].(string)
		} else {
			versionInfo.Cluster.Arch = "unknown-arch"
		}
	}

	outputFormat := CharReplacer.Replace(options.Format)
	if outputFormat == "" {
		outputFormat = GetCommandOptionWithDefault("version", "format", DefaultFormat)
	}
	if options.Quiet {
		outputFormat = "{{.Client.Version}}"
	}

	result := CommandResult{
		Format:    format.Format(outputFormat),
		OutputAs:  toOutputType(options.OutputAs),
		NameLimit: options.NameLimit,
		Data:      versionInfo,
	}

	GenerateOutput(&result)
	return nil
}
