package service

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"time"

	cda "git.loafle.net/commons_go/di/annotation"
	cdr "git.loafle.net/commons_go/di/registry"
	"git.loafle.net/commons_go/logging"
	crc "git.loafle.net/commons_go/rpc/client"
	crr "git.loafle.net/commons_go/rpc/registry"
	oocmci "git.loafle.net/overflow/overflow_commons_go/modules/commons/interfaces"
	"git.loafle.net/overflow/overflow_probes/client/container"
)

func init() {
	cdr.RegisterType(reflect.TypeOf((*ContainerService)(nil)))
}

type containerState struct {
	pid    int
	port   int
	client crc.Client
}

type ContainerService struct {
	cda.TypeAnnotation `annotation:"@overFlow:Service()"`

	oocmci.Service

	ProbeRPCInvoker crr.RPCInvoker `annotation:"@Resource()"`

	clients map[string]*containerState
}

func (cs *ContainerService) Start() error {

	return nil
}

func (cs *ContainerService) Stop(ctx context.Context) error {

	return nil
}

func (cs *ContainerService) Call(name string, result interface{}, method string, params ...interface{}) error {
	c, err := cs.GetClient(name)
	if nil != err {
		return err
	}
	return c.Call(result, method, params...)
}

func (cs *ContainerService) Send(name string, method string, params ...interface{}) error {
	c, err := cs.GetClient(name)
	if nil != err {
		return err
	}
	return c.Send(method, params...)
}

func (cs *ContainerService) GetClient(name string) (crc.Client, error) {
	cState, ok := cs.clients[name]
	if !ok {
		if err := cs.runProcess(name); nil != err {
			return nil, err
		}
		cState, _ = cs.clients[name]
	}

	return cState.client, nil
}

func (cs *ContainerService) Connect(name string) error {
	return nil
}

func (cs *ContainerService) runProcess(name string) error {
	ok := cs.checkProcess(name)

	if ok {
		return nil
	}

	cmd, pidPath := container.GetContainerCommand(name)
	removePidFile(pidPath)

	if err := cmd.Start(); nil != err {
		logging.Logger().Errorf("Probe: To run container(%s) failed err %v", name, err)
		return err
	}

	port, err := watchPidFileCreate(pidPath, time.Duration(time.Second*2))
	if nil != err {
		return err
	}
	// port := 60000

	client := container.New(port, cs.ProbeRPCInvoker)
	if err := client.Connect(); nil != err {
		cmd.Process.Kill()
		return err
	}

	logging.Logger().Debugf("Container[%s] pid: %d", name, cmd.Process.Pid)

	cState := &containerState{
		pid:    cmd.Process.Pid,
		port:   port,
		client: client,
	}

	if nil == cs.clients {
		cs.clients = make(map[string]*containerState, 0)
	}
	cs.clients[name] = cState

	return nil
}

func (cs *ContainerService) checkProcess(name string) bool {
	cState, ok := cs.clients[name]
	if !ok || nil == cState || nil == cState.client {
		return false
	}

	_, err := os.FindProcess(cState.pid)
	if nil != err {
		return false
	}

	return true
}

func (cs *ContainerService) killProcess(name string) error {
	cState, ok := cs.clients[name]
	if !ok || nil == cState || nil == cState.client {
		return fmt.Errorf("Probe: Container[%s] is not exist", name)
	}

	p, err := os.FindProcess(cState.pid)
	if nil != err {
		return err
	}

	if err = p.Signal(os.Interrupt); nil != err {
		return err
	}
	// if err = p.Kill(); nil != err {
	// 	return err
	// }

	return nil
}

func removePidFile(pidFilePath string) {
	if _, err := os.Stat(pidFilePath); err == nil {
		if err := os.Remove(pidFilePath); nil != err {
			logging.Logger().Errorf("Probe: Removing pid file has been failed [%v]", err)
		}
	}
}

func watchPidFileCreate(pidFilePath string, waitTime time.Duration) (int, error) {
	startTime := time.Now()

	for {
		if _, err := os.Stat(pidFilePath); err == nil {
			buf, err := ioutil.ReadFile(pidFilePath)
			if nil != err {
				return 0, err
			}
			portNumber, err := strconv.ParseInt(string(buf), 10, 32)
			if nil != err {
				return 0, err
			}
			return int(portNumber), nil
		}

		if time.Since(startTime) > waitTime {
			return 0, fmt.Errorf("Probe: pid file not exist")
		}

		time.Sleep(time.Duration(time.Millisecond * 100))
	}
}
