package configs

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/avast/retry-go/v5"
	"github.com/mitchellh/hashstructure/v2"
	"github.com/pelletier/go-toml/v2"
	"go.uber.org/multierr"
)

const (
	defaultConcurrency    = 1
	defaultCheckpointPath = "/tmp/backup-worker/checkpoint"

	defaultRetryAttempts = 3
	defaultRetryInterval = 5 * time.Second

	defaultVmMatch    = `{__name__=~".*"}`
	defaultVmInterval = time.Hour
	defaultVmTimeout  = 5 * time.Minute
)

type Duration time.Duration

func (d *Duration) UnmarshalText(b []byte) error {
	x, err := time.ParseDuration(string(b))
	if err != nil {
		return err
	}
	*d = Duration(x)
	return nil
}

const (
	ModeMetric    = "metric"
	ModeLog       = "log"
	ModeDeltalake = "deltalake"
)

type Config struct {
	Mode        string      `toml:"mode"`
	Sources     Sources     `toml:"sources"`
	Sinks       Sinks       `toml:"sinks"`
	Syncers     Syncers     `toml:"syncers"`
	Execution   Execution   `toml:"execution"`
	RetryPolicy RetryPolicy `toml:"retryPolicy"`
	Checkpoint  Checkpoint  `toml:"checkpoint"`
}

func (c *Config) NormalizeAndValidate() error {
	var errs error
	if c.Mode == "" {
		c.Mode = ModeMetric
	}

	errs = multierr.Append(errs, c.Execution.Validate())
	errs = multierr.Append(errs, c.RetryPolicy.Validate())
	errs = multierr.Append(errs, c.Checkpoint.Validate())

	switch c.Mode {
	case ModeLog, ModeDeltalake:
		errs = multierr.Append(errs, c.Syncers.Validate(c.Mode))
	case ModeMetric:
		errs = multierr.Append(errs, c.Sources.Validate())
		errs = multierr.Append(errs, c.Sinks.Validate())
	default:
		errs = multierr.Append(errs, fmt.Errorf("invalid mode: %s", c.Mode))
	}
	return errs
}

// ID generates a unique identifier for this configuration using structural hashing.
//
// This ID enables idempotent backup operations: the same configuration always
// produces the same ID, allowing the system to safely resume interrupted backups
// and deduplicate backup artifacts in object storage.
func (c *Config) ID() string {
	hash, _ := hashstructure.Hash(c, hashstructure.FormatV2, nil)
	return fmt.Sprintf("%x", hash)
}

type Execution struct {
	Concurrency int `toml:"concurrency"`
}

func (c *Execution) Validate() error {
	if c.Concurrency <= 0 {
		c.Concurrency = defaultConcurrency
	}
	return nil
}

type RetryPolicy struct {
	RetryMaxAttempts int      `toml:"retryMaxAttempts"`
	RetryInterval    Duration `toml:"retryInterval"`
}

func (c *RetryPolicy) Validate() error {
	if c.RetryMaxAttempts <= 0 {
		c.RetryMaxAttempts = defaultRetryAttempts
	}
	if c.RetryInterval <= 0 {
		c.RetryInterval = Duration(defaultRetryInterval)
	}
	return nil
}

func (c *RetryPolicy) Do(ctx context.Context, f func() error) error {
	return retry.New(
		retry.Context(ctx),
		retry.Attempts(uint(c.RetryMaxAttempts)),
		retry.Delay(time.Duration(c.RetryInterval)),
	).Do(f)
}

type Checkpoint struct {
	Path string `toml:"path"`
}

func (c *Checkpoint) Validate() error {
	if c.Path == "" {
		c.Path = defaultCheckpointPath
	}
	return nil
}

// Load reads and validates a configuration file from the given path.
//
// Validation happens immediately after loading to fail fast before any
// backup operations begin, when errors are easier to diagnose and fix.
func Load(path string) (*Config, error) {
	b, err := os.ReadFile(filepath.Clean(path))
	if err != nil {
		return nil, fmt.Errorf("read config: %w", err)
	}
	var cfg Config
	if err := toml.Unmarshal(b, &cfg); err != nil {
		return nil, fmt.Errorf("parse toml: %w", err)
	}
	if err := cfg.NormalizeAndValidate(); err != nil {
		return nil, err
	}
	return &cfg, nil
}
