package tasks

import (
	"fmt"
	"path/filepath"

	"github.com/giolekva/pcloud/core/installer"
	"github.com/giolekva/pcloud/core/installer/cluster"
	"github.com/giolekva/pcloud/core/installer/soft"
	"github.com/giolekva/pcloud/core/installer/status"
)

type InstallFunc func() (installer.ReleaseResources, error)

type dynamicTaskSlice struct {
	t []Task
}

func (d *dynamicTaskSlice) Tasks() []Task {
	return d.t
}

func (d *dynamicTaskSlice) Append(t Task) {
	d.t = append(d.t, t)
}

func NewInstallTask(mon status.ResourceMonitor, fn InstallFunc) Task {
	d := &dynamicTaskSlice{t: []Task{}}
	var rr installer.ReleaseResources
	done := make(chan error)
	installTask := newLeafTask("Downloading configuration files", func() error {
		var err error
		rr, err = fn()
		return err
	})
	d.Append(&installTask)
	installTask.OnDone(func(err error) {
		if err != nil {
			done <- err
			return
		}
		monTasks := NewMonitorReleaseTasks(mon, rr)
		for _, mt := range monTasks {
			d.Append(mt)
		}
		monitor := newConcurrentParentTask("Monitor", true, monTasks...)
		monitor.OnDone(func(err error) {
			done <- err
		})
		monitor.Start()
	})
	start := func() error {
		installTask.Start()
		return <-done
	}
	t := newParentTask("Installing application", true, start, d)
	return &t
}

func NewClusterInitTask(m cluster.Manager, server cluster.Server, cnc installer.ClusterNetworkConfigurator, repo soft.RepoIO, setupFn cluster.ClusterIngressSetupFunc) Task {
	d := &dynamicTaskSlice{t: []Task{}}
	done := make(chan error)
	setupTask := newLeafTask(fmt.Sprintf("Installing dodo on %s", server.IP.String()), func() error {
		_, err := m.Init(server, setupFn)
		return err
	})
	d.Append(&setupTask)
	setupTask.OnDone(func(err error) {
		if err != nil {
			done <- err
			return
		}
		if err := cnc.AddCluster(m.State().Name, m.State().IngressIP); err != nil {
			done <- err
			return
		}
		_, err = repo.Do(func(fs soft.RepoFS) (string, error) {
			if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", m.State().Name), m.State()); err != nil {
				return "", err
			}
			return fmt.Sprintf("add server to cluster: %s", m.State().Name), nil
		})
		done <- err
	})
	start := func() error {
		setupTask.Start()
		return <-done
	}
	t := newParentTask("Installing application", true, start, d)
	return &t
}

func NewRemoveClusterTask(m cluster.Manager, cnc installer.ClusterNetworkConfigurator, repo soft.RepoIO) Task {
	t := newLeafTask(fmt.Sprintf("Removing %s cluster", m.State().Name), func() error {
		if err := cnc.RemoveCluster(m.State().Name, m.State().IngressIP); err != nil {
			return err
		}
		_, err := repo.Do(func(fs soft.RepoFS) (string, error) {
			if err := fs.RemoveAll(fmt.Sprintf("/clusters/%s", m.State().Name)); err != nil {
				return "", err
			}
			kustPath := filepath.Join("/clusters", "kustomization.yaml")
			kust, err := soft.ReadKustomization(fs, kustPath)
			if err != nil {
				return "", err
			}
			kust.RemoveResources(m.State().Name)
			soft.WriteYaml(fs, kustPath, kust)
			return fmt.Sprintf("remove cluster: %s", m.State().Name), nil
		})
		return err
	})
	return &t
}

func NewClusterJoinControllerTask(m cluster.Manager, server cluster.Server, repo soft.RepoIO) Task {
	d := &dynamicTaskSlice{t: []Task{}}
	done := make(chan error)
	setupTask := newLeafTask(fmt.Sprintf("Joining %s to %s cluster", server.IP.String(), m.State().Name), func() error {
		return m.JoinController(server)
	})
	d.Append(&setupTask)
	setupTask.OnDone(func(err error) {
		if err != nil {
			done <- err
			return
		}
		_, err = repo.Do(func(fs soft.RepoFS) (string, error) {
			if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", m.State().Name), m.State()); err != nil {
				return "", err
			}
			return fmt.Sprintf("add controller server to cluster: %s", m.State().Name), nil
		})
		done <- err
	})
	start := func() error {
		setupTask.Start()
		return <-done
	}
	t := newParentTask("Installing application", true, start, d)
	return &t
}

func NewClusterJoinWorkerTask(m cluster.Manager, server cluster.Server, repo soft.RepoIO) Task {
	d := &dynamicTaskSlice{t: []Task{}}
	done := make(chan error)
	setupTask := newLeafTask(fmt.Sprintf("Joining %s to %s cluster", server.IP.String(), m.State().Name), func() error {
		return m.JoinWorker(server)
	})
	d.Append(&setupTask)
	setupTask.OnDone(func(err error) {
		if err != nil {
			done <- err
			return
		}
		_, err = repo.Do(func(fs soft.RepoFS) (string, error) {
			if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", m.State().Name), m.State()); err != nil {
				return "", err
			}
			return fmt.Sprintf("add worker server to cluster: %s", m.State().Name), nil
		})
		done <- err
	})
	start := func() error {
		setupTask.Start()
		return <-done
	}
	t := newParentTask("Installing application", true, start, d)
	return &t
}

func NewClusterRemoveServerTask(m cluster.Manager, server string, repo soft.RepoIO) Task {
	d := &dynamicTaskSlice{t: []Task{}}
	done := make(chan error)
	setupTask := newLeafTask(fmt.Sprintf("Removing %s from %s cluster", server, m.State().Name), func() error {
		return m.RemoveServer(server)
	})
	d.Append(&setupTask)
	setupTask.OnDone(func(err error) {
		if err != nil {
			done <- err
			return
		}
		_, err = repo.Do(func(fs soft.RepoFS) (string, error) {
			if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", m.State().Name), m.State()); err != nil {
				return "", err
			}
			return fmt.Sprintf("remove %s from cluster: %s", server, m.State().Name), nil
		})
		done <- err
	})
	start := func() error {
		setupTask.Start()
		return <-done
	}
	t := newParentTask("Installing application", true, start, d)
	return &t
}

func NewClusterSetupTask(m cluster.Manager, setupFn cluster.ClusterSetupFunc, repo soft.RepoIO, msg string) Task {
	d := &dynamicTaskSlice{t: []Task{}}
	done := make(chan error)
	setupTask := newLeafTask(msg, func() error {
		return setupFn(m)
	})
	d.Append(&setupTask)
	setupTask.OnDone(func(err error) {
		if err != nil {
			done <- err
			return
		}
		_, err = repo.Do(func(fs soft.RepoFS) (string, error) {
			if err := soft.WriteJson(fs, fmt.Sprintf("/clusters/%s/config.json", m.State().Name), m.State()); err != nil {
				return "", err
			}
			return msg, nil
		})
		done <- err
	})
	start := func() error {
		setupTask.Start()
		return <-done
	}
	t := newParentTask("Installing application", true, start, d)
	return &t
}
