package server

import (
	"encoding/json"
	"fmt"
	"net"
	"os"
	"sync/atomic"

	"repositories.action2quare.com/ayo/gocommon/logger"
	"repositories.action2quare.com/ayo/houston/client"
	"repositories.action2quare.com/ayo/houston/shared"
	"repositories.action2quare.com/ayo/houston/shared/protos"

	"google.golang.org/grpc"
)

// protoc --go_out=. --go-grpc_out=. protos/*.proto
type HoustonServer interface {
	Start() error
	Stop()
	Operation() Operation
}

type serverConfig struct {
	GrpcPort    int    `json:"grpc_port"`
	StorageRoot string `json:"storage_path"`
	RunAsClient bool   `json:"run_as_client"`
}

type DeployRequest struct {
	shared.DeployRequest
	hostnames []string
}

func MakeDeployRequest(req shared.DeployRequest, targets []string) DeployRequest {
	return DeployRequest{
		DeployRequest: req,
		hostnames:     targets,
	}
}

type WithdrawRequest struct {
	shared.WithdrawRequest
	hostnames []string
}

func MakeWithdrawRequest(req shared.WithdrawRequest, targets []string) WithdrawRequest {
	return WithdrawRequest{
		WithdrawRequest: req,
		hostnames:       targets,
	}
}

type StartProcessRequest struct {
	shared.StartProcessRequest
	hostnames []string
}

func MakeStartProcessRequest(req shared.StartProcessRequest, targets []string) StartProcessRequest {
	return StartProcessRequest{
		StartProcessRequest: req,
		hostnames:           targets,
	}
}

type StopProcessRequest struct {
	shared.StopProcessRequest
	hostnames []string
}

func MakeStopProcessRequest(req shared.StopProcessRequest, targets []string) StopProcessRequest {
	return StopProcessRequest{
		StopProcessRequest: req,
		hostnames:          targets,
	}
}

type RestartProcessRequest struct {
	shared.RestartProcessRequest
	hostnames []string
}

func MakeRestartRequest(req shared.RestartProcessRequest, targets []string) RestartProcessRequest {
	return RestartProcessRequest{
		RestartProcessRequest: req,
		hostnames:             targets,
	}
}

type UploadRequest struct {
	shared.UploadRequest
	hostnames []string
}

func MakeUploadRequest(req shared.UploadRequest, targets []string) UploadRequest {
	return UploadRequest{
		UploadRequest: req,
		hostnames:     targets,
	}
}

type Operation interface {
	Deploy(DeployRequest)
	Withdraw(WithdrawRequest)
	StartProcess(StartProcessRequest)
	StopProcess(StopProcessRequest)
	RestartProcess(RestartProcessRequest)
	Upload(UploadRequest)
	Hosts() map[string]hostSnapshot
	DeplyingProgress() []*protos.DeployingProgress
}

func loadServerConfig() serverConfig {
	configFile, err := os.Open("config.json")
	if err != nil {
		logger.Println(err)
		return serverConfig{
			GrpcPort: 8080,
		}
	}
	defer configFile.Close()

	var config struct {
		Houston *struct {
			Server serverConfig `json:"server"`
		} `json:"houston"`
	}

	dec := json.NewDecoder(configFile)
	err = dec.Decode(&config)
	if err != nil {
		logger.Println(err)
		return serverConfig{
			GrpcPort: 8080,
		}
	}

	if config.Houston == nil {
		logger.Println(`"houston" object is missing in config.json`)
		return serverConfig{
			GrpcPort: 8080,
		}
	}

	return config.Houston.Server
}

func NewServer() HoustonServer {
	var opts []grpc.ServerOption
	grpcServer := grpc.NewServer(opts...)

	os := newOperationServer()
	ms := newMonitorServer()
	protos.RegisterOperationServer(grpcServer, os)
	protos.RegisterMonitorServer(grpcServer, ms)

	return &houstonServer{
		rpcServer: grpcServer,
		os:        os,
		ms:        ms,
		port:      loadServerConfig().GrpcPort,
	}
}

type houstonServer struct {
	rpcServer *grpc.Server
	os        *operationServer
	ms        *monitorServer
	port      int
}

func (hs *houstonServer) Start() error {
	logger.Println("houston server is started at port", hs.port)
	lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", hs.port))
	if err != nil {
		return err
	}

	closeCount := int32(0)
	var hc client.HoustonClient
	if loadServerConfig().RunAsClient {
		hc, err = client.NewClient(false)
		if err != nil {
			return err
		}

		go func() {
			hc.Start()
			if atomic.AddInt32(&closeCount, 1) == 1 {
				hs.Stop()
			}
		}()
	}

	err = hs.rpcServer.Serve(lis)
	if atomic.AddInt32(&closeCount, 1) == 1 {
		if hc != nil {
			hc.Shutdown()
		}
	}

	return err
}

func (hs *houstonServer) Stop() {
	hs.rpcServer.Stop()
}

func (hs *houstonServer) Operation() Operation {
	return hs.os
}
