package env_v0_4

import (
	"encoding/json"
	"fmt"
	"github.com/openziti/zrok/environment/env_core"
	"github.com/openziti/zrok/environment/env_v0_3"
	"github.com/pkg/errors"
	"os"
	"path/filepath"
)

const V = "v0.4"

type Root struct {
	meta *env_core.Metadata
	cfg  *env_core.Config
	env  *env_core.Environment
}

func Default() (*Root, error) {
	r := &Root{}
	root, err := rootDir()
	if err != nil {
		return nil, err
	}
	r.meta = &env_core.Metadata{
		V:        V,
		RootPath: root,
	}
	return r, nil
}

func Assert() (bool, error) {
	exists, err := rootExists()
	if err != nil {
		return true, err
	}
	if exists {
		meta, err := loadMetadata()
		if err != nil {
			return true, err
		}
		return meta.V == V, nil
	}
	return false, nil
}

func Load() (*Root, error) {
	r := &Root{}
	exists, err := rootExists()
	if err != nil {
		return nil, err
	}
	if exists {
		if meta, err := loadMetadata(); err == nil {
			r.meta = meta
		} else {
			return nil, err
		}

		if cfg, err := loadConfig(); err == nil {
			r.cfg = cfg
		}

		if env, err := loadEnvironment(); err == nil {
			r.env = env
		}

	} else {
		root, err := Default()
		if err != nil {
			return nil, err
		}
		r = root
	}
	return r, nil
}

func Update(r env_core.Root) (env_core.Root, error) {
	if r == nil || r.Metadata() == nil {
		return nil, errors.Errorf("nil root")
	}
	if r.Metadata().V != env_v0_3.V {
		return nil, errors.Errorf("expecting version '%v'", env_v0_3.V)
	}

	newR := &Root{meta: r.Metadata(), cfg: r.Config(), env: r.Environment()}

	oldAccessF, err := r.ZitiIdentityNamed(r.PublicIdentityName())
	if err != nil {
		return nil, err
	}
	_, err = os.Stat(oldAccessF)
	if err == nil {
		newAccessF, err := newR.ZitiIdentityNamed(newR.PublicIdentityName())
		if err != nil {
			return nil, err
		}
		if err := os.Rename(oldAccessF, newAccessF); err != nil {
			return nil, err
		}
		fmt.Printf("renamed '%v' -> '%v'\n", oldAccessF, newAccessF)
	} else if !os.IsNotExist(err) {
		return nil, err
	}

	oldShareF, err := r.ZitiIdentityNamed(r.EnvironmentIdentityName())
	if err != nil {
		return nil, err
	}
	_, err = os.Stat(oldShareF)
	if err == nil {
		newShareF, err := newR.ZitiIdentityNamed(newR.EnvironmentIdentityName())
		if err != nil {
			return nil, err
		}
		if err := os.Rename(oldShareF, newShareF); err != nil {
			return nil, err
		}
		fmt.Printf("renamed '%v' -> '%v'\n", oldShareF, newShareF)
	} else if !os.IsNotExist(err) {
		return nil, err
	}

	if err := writeMetadata(); err != nil {
		return nil, err
	}

	meta, err := loadMetadata()
	if err != nil {
		return nil, err
	}
	newR.meta = meta

	return newR, nil
}

func rootExists() (bool, error) {
	mf, err := metadataFile()
	if err != nil {
		return false, err
	}
	_, err = os.Stat(mf)
	if os.IsNotExist(err) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

func assertMetadata() error {
	exists, err := rootExists()
	if err != nil {
		return err
	}
	if !exists {
		if err := writeMetadata(); err != nil {
			return err
		}
	}
	return nil
}

func loadMetadata() (*env_core.Metadata, error) {
	mf, err := metadataFile()
	if err != nil {
		return nil, err
	}
	data, err := os.ReadFile(mf)
	if err != nil {
		return nil, err
	}
	m := &metadata{}
	if err := json.Unmarshal(data, m); err != nil {
		return nil, errors.Wrapf(err, "error unmarshaling metadata file '%v'", mf)
	}
	if m.V != V {
		return nil, errors.Errorf("got metadata version '%v', expected '%v'", m.V, V)
	}
	rf, err := rootDir()
	if err != nil {
		return nil, err
	}
	out := &env_core.Metadata{
		V:        m.V,
		RootPath: rf,
	}
	return out, nil
}

func writeMetadata() error {
	mf, err := metadataFile()
	if err != nil {
		return err
	}
	data, err := json.Marshal(&metadata{V: V})
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(mf), os.FileMode(0700)); err != nil {
		return err
	}
	if err := os.WriteFile(mf, data, os.FileMode(0600)); err != nil {
		return err
	}
	return nil
}

func loadConfig() (*env_core.Config, error) {
	cf, err := configFile()
	if err != nil {
		return nil, errors.Wrap(err, "error getting config file path")
	}
	data, err := os.ReadFile(cf)
	if err != nil {
		return nil, errors.Wrapf(err, "error reading config file '%v'", cf)
	}
	cfg := &config{}
	if err := json.Unmarshal(data, cfg); err != nil {
		return nil, errors.Wrapf(err, "error unmarshaling config file '%v'", cf)
	}
	out := &env_core.Config{
		ApiEndpoint:     cfg.ApiEndpoint,
		DefaultFrontend: cfg.DefaultFrontend,
	}
	return out, nil
}

func saveConfig(cfg *env_core.Config) error {
	in := &config{ApiEndpoint: cfg.ApiEndpoint, DefaultFrontend: cfg.DefaultFrontend}
	data, err := json.MarshalIndent(in, "", "  ")
	if err != nil {
		return errors.Wrap(err, "error marshaling config")
	}
	cf, err := configFile()
	if err != nil {
		return errors.Wrap(err, "error getting config file path")
	}
	if err := os.MkdirAll(filepath.Dir(cf), os.FileMode(0700)); err != nil {
		return errors.Wrapf(err, "error creating environment path '%v'", filepath.Dir(cf))
	}
	if err := os.WriteFile(cf, data, os.FileMode(0600)); err != nil {
		return errors.Wrap(err, "error saving config file")
	}
	return nil
}

func isEnabled() (bool, error) {
	ef, err := environmentFile()
	if err != nil {
		return false, errors.Wrap(err, "error getting environment file path")
	}
	_, err = os.Stat(ef)
	if os.IsNotExist(err) {
		return false, nil
	}
	if err != nil {
		return false, errors.Wrapf(err, "error stat-ing environment file '%v'", ef)
	}
	return true, nil
}

func loadEnvironment() (*env_core.Environment, error) {
	ef, err := environmentFile()
	if err != nil {
		return nil, errors.Wrap(err, "error getting environment file")
	}
	data, err := os.ReadFile(ef)
	if err != nil {
		return nil, errors.Wrapf(err, "error reading environment file '%v'", ef)
	}
	env := &environment{}
	if err := json.Unmarshal(data, env); err != nil {
		return nil, errors.Wrapf(err, "error unmarshaling environment file '%v'", ef)
	}
	out := &env_core.Environment{
		Token:        env.Token,
		ZitiIdentity: env.ZId,
		ApiEndpoint:  env.ApiEndpoint,
	}
	return out, nil
}

func saveEnvironment(env *env_core.Environment) error {
	in := &environment{
		Token:       env.Token,
		ZId:         env.ZitiIdentity,
		ApiEndpoint: env.ApiEndpoint,
	}
	data, err := json.MarshalIndent(in, "", "  ")
	if err != nil {
		return errors.Wrap(err, "error marshaling environment")
	}
	ef, err := environmentFile()
	if err != nil {
		return errors.Wrap(err, "error getting environment file")
	}
	if err := os.MkdirAll(filepath.Dir(ef), os.FileMode(0700)); err != nil {
		return errors.Wrapf(err, "error creating environment path '%v'", filepath.Dir(ef))
	}
	if err := os.WriteFile(ef, data, os.FileMode(0600)); err != nil {
		return errors.Wrap(err, "error saving environment file")
	}
	return nil
}

func deleteEnvironment() error {
	ef, err := environmentFile()
	if err != nil {
		return errors.Wrap(err, "error getting environment file")
	}
	if err := os.Remove(ef); err != nil {
		return errors.Wrap(err, "error removing environment file")
	}

	return nil
}

type metadata struct {
	V string `json:"v"`
}

type config struct {
	ApiEndpoint     string `json:"api_endpoint"`
	DefaultFrontend string `json:"default_frontend"`
}

type environment struct {
	Token       string `json:"zrok_token"`
	ZId         string `json:"ziti_identity"`
	ApiEndpoint string `json:"api_endpoint"`
}