package eureka

import (
	"errors"
	"fmt"
	"reflect"
	"sync"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/hudl/fargo"
)

type testConnection struct {
	mu        sync.RWMutex
	instances []*fargo.Instance

	errApplication error
	errHeartbeat   error
	errRegister    error
	errDeregister  error
}

var (
	errTest       = errors.New("kaboom")
	errNotFound   = &fargoUnsuccessfulHTTPResponse{statusCode: 404, messagePrefix: "not found"}
	loggerTest    = log.NewNopLogger()
	appNameTest   = "go-kit"
	instanceTest1 = &fargo.Instance{
		HostName:         "serveregistrar1.acme.org",
		Port:             8080,
		App:              appNameTest,
		IPAddr:           "192.168.0.1",
		VipAddress:       "192.168.0.1",
		SecureVipAddress: "192.168.0.1",
		HealthCheckUrl:   "http://serveregistrar1.acme.org:8080/healthz",
		StatusPageUrl:    "http://serveregistrar1.acme.org:8080/status",
		HomePageUrl:      "http://serveregistrar1.acme.org:8080/",
		Status:           fargo.UP,
		DataCenterInfo:   fargo.DataCenterInfo{Name: fargo.MyOwn},
		LeaseInfo:        fargo.LeaseInfo{RenewalIntervalInSecs: 1},
	}
	instanceTest2 = &fargo.Instance{
		HostName:         "serveregistrar2.acme.org",
		Port:             8080,
		App:              appNameTest,
		IPAddr:           "192.168.0.2",
		VipAddress:       "192.168.0.2",
		SecureVipAddress: "192.168.0.2",
		HealthCheckUrl:   "http://serveregistrar2.acme.org:8080/healthz",
		StatusPageUrl:    "http://serveregistrar2.acme.org:8080/status",
		HomePageUrl:      "http://serveregistrar2.acme.org:8080/",
		Status:           fargo.UP,
		DataCenterInfo:   fargo.DataCenterInfo{Name: fargo.MyOwn},
	}
)

var _ fargoConnection = (*testConnection)(nil)

func (c *testConnection) RegisterInstance(i *fargo.Instance) error {
	if c.errRegister != nil {
		return c.errRegister
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	for _, instance := range c.instances {
		if reflect.DeepEqual(*instance, *i) {
			return errors.New("already registered")
		}
	}
	c.instances = append(c.instances, i)
	return nil
}

func (c *testConnection) HeartBeatInstance(i *fargo.Instance) error {
	return c.errHeartbeat
}

func (c *testConnection) DeregisterInstance(i *fargo.Instance) error {
	if c.errDeregister != nil {
		return c.errDeregister
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	remaining := make([]*fargo.Instance, 0, len(c.instances))
	for _, instance := range c.instances {
		if reflect.DeepEqual(*instance, *i) {
			continue
		}
		remaining = append(remaining, instance)
	}
	if len(remaining) == len(c.instances) {
		return errors.New("not registered")
	}
	c.instances = remaining
	return nil
}

func (c *testConnection) ReregisterInstance(ins *fargo.Instance) error {
	return nil
}

func (c *testConnection) instancesForApplication(name string) []*fargo.Instance {
	c.mu.RLock()
	defer c.mu.RUnlock()
	instances := make([]*fargo.Instance, 0, len(c.instances))
	for _, i := range c.instances {
		if i.App == name {
			instances = append(instances, i)
		}
	}
	return instances
}

func (c *testConnection) GetApp(name string) (*fargo.Application, error) {
	if err := c.errApplication; err != nil {
		return nil, err
	}
	instances := c.instancesForApplication(name)
	if len(instances) == 0 {
		return nil, fmt.Errorf("Application not found for name=%s", name)
	}
	return &fargo.Application{Name: name, Instances: instances}, nil
}

func (c *testConnection) ScheduleAppUpdates(name string, await bool, done <-chan struct{}) <-chan fargo.AppUpdate {
	updatec := make(chan fargo.AppUpdate, 1)
	send := func() {
		app, err := c.GetApp(name)
		select {
		case updatec <- fargo.AppUpdate{App: app, Err: err}:
		default:
		}
	}

	if await {
		send()
	}
	go func() {
		ticker := time.NewTicker(100 * time.Millisecond)
		for {
			select {
			case <-ticker.C:
				send()
			case <-done:
				ticker.Stop()
				return
			}
		}
	}()
	return updatec
}
