package dailysecret

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
)

const (
	fileName      = "daily_secrets.json"
	schemaVersion = 1
)

type ConnectionBundle struct {
	Password             string `json:"password,omitempty"`
	SSHPassword          string `json:"sshPassword,omitempty"`
	ProxyPassword        string `json:"proxyPassword,omitempty"`
	HTTPTunnelPassword   string `json:"httpTunnelPassword,omitempty"`
	MySQLReplicaPassword string `json:"mysqlReplicaPassword,omitempty"`
	MongoReplicaPassword string `json:"mongoReplicaPassword,omitempty"`
	OpaqueURI            string `json:"opaqueURI,omitempty"`
	OpaqueDSN            string `json:"opaqueDSN,omitempty"`
}

func (b ConnectionBundle) HasAny() bool {
	return strings.TrimSpace(b.Password) != "" ||
		strings.TrimSpace(b.SSHPassword) != "" ||
		strings.TrimSpace(b.ProxyPassword) != "" ||
		strings.TrimSpace(b.HTTPTunnelPassword) != "" ||
		strings.TrimSpace(b.MySQLReplicaPassword) != "" ||
		strings.TrimSpace(b.MongoReplicaPassword) != "" ||
		strings.TrimSpace(b.OpaqueURI) != "" ||
		strings.TrimSpace(b.OpaqueDSN) != ""
}

type GlobalProxyBundle struct {
	Password string `json:"password,omitempty"`
}

func (b GlobalProxyBundle) HasAny() bool {
	return strings.TrimSpace(b.Password) != ""
}

type ProviderBundle struct {
	APIKey           string            `json:"apiKey,omitempty"`
	SensitiveHeaders map[string]string `json:"sensitiveHeaders,omitempty"`
}

func (b ProviderBundle) HasAny() bool {
	return strings.TrimSpace(b.APIKey) != "" || len(b.SensitiveHeaders) > 0
}

type File struct {
	SchemaVersion int                         `json:"schemaVersion,omitempty"`
	Connections   map[string]ConnectionBundle `json:"connections,omitempty"`
	GlobalProxy   *GlobalProxyBundle          `json:"globalProxy,omitempty"`
	AIProviders   map[string]ProviderBundle   `json:"aiProviders,omitempty"`
}

type Store struct {
	root string
}

func NewStore(root string) *Store {
	return &Store{root: strings.TrimSpace(root)}
}

func (s *Store) Path() string {
	return filepath.Join(s.root, fileName)
}

func (s *Store) Load() (File, error) {
	if strings.TrimSpace(s.root) == "" {
		return File{SchemaVersion: schemaVersion}, nil
	}
	data, err := os.ReadFile(s.Path())
	if err != nil {
		if os.IsNotExist(err) {
			return File{SchemaVersion: schemaVersion}, nil
		}
		return File{}, err
	}
	var file File
	if err := json.Unmarshal(data, &file); err != nil {
		return File{}, err
	}
	if file.SchemaVersion == 0 {
		file.SchemaVersion = schemaVersion
	}
	return file, nil
}

func (s *Store) Save(file File) error {
	if strings.TrimSpace(s.root) == "" {
		return nil
	}
	file.SchemaVersion = schemaVersion
	if len(file.Connections) == 0 {
		file.Connections = nil
	}
	if file.GlobalProxy != nil && !file.GlobalProxy.HasAny() {
		file.GlobalProxy = nil
	}
	if len(file.AIProviders) == 0 {
		file.AIProviders = nil
	}
	if err := os.MkdirAll(s.root, 0o755); err != nil {
		return err
	}
	payload, err := json.MarshalIndent(file, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(s.Path(), payload, 0o644)
}

func (s *Store) GetConnection(id string) (ConnectionBundle, bool, error) {
	file, err := s.Load()
	if err != nil {
		return ConnectionBundle{}, false, err
	}
	bundle, ok := file.Connections[strings.TrimSpace(id)]
	return bundle, ok, nil
}

func (s *Store) PutConnection(id string, bundle ConnectionBundle) error {
	file, err := s.Load()
	if err != nil {
		return err
	}
	if !bundle.HasAny() {
		return s.deleteConnectionFromFile(file, id)
	}
	if file.Connections == nil {
		file.Connections = make(map[string]ConnectionBundle)
	}
	file.Connections[strings.TrimSpace(id)] = bundle
	return s.Save(file)
}

func (s *Store) DeleteConnection(id string) error {
	file, err := s.Load()
	if err != nil {
		return err
	}
	return s.deleteConnectionFromFile(file, id)
}

func (s *Store) deleteConnectionFromFile(file File, id string) error {
	if len(file.Connections) != 0 {
		delete(file.Connections, strings.TrimSpace(id))
	}
	return s.Save(file)
}

func (s *Store) GetGlobalProxy() (GlobalProxyBundle, bool, error) {
	file, err := s.Load()
	if err != nil {
		return GlobalProxyBundle{}, false, err
	}
	if file.GlobalProxy == nil {
		return GlobalProxyBundle{}, false, nil
	}
	return *file.GlobalProxy, true, nil
}

func (s *Store) PutGlobalProxy(bundle GlobalProxyBundle) error {
	file, err := s.Load()
	if err != nil {
		return err
	}
	if !bundle.HasAny() {
		file.GlobalProxy = nil
		return s.Save(file)
	}
	copyBundle := bundle
	file.GlobalProxy = &copyBundle
	return s.Save(file)
}

func (s *Store) DeleteGlobalProxy() error {
	file, err := s.Load()
	if err != nil {
		return err
	}
	file.GlobalProxy = nil
	return s.Save(file)
}

func (s *Store) GetAIProvider(id string) (ProviderBundle, bool, error) {
	file, err := s.Load()
	if err != nil {
		return ProviderBundle{}, false, err
	}
	bundle, ok := file.AIProviders[strings.TrimSpace(id)]
	return bundle, ok, nil
}

func (s *Store) PutAIProvider(id string, bundle ProviderBundle) error {
	file, err := s.Load()
	if err != nil {
		return err
	}
	if !bundle.HasAny() {
		return s.deleteAIProviderFromFile(file, id)
	}
	if file.AIProviders == nil {
		file.AIProviders = make(map[string]ProviderBundle)
	}
	if len(bundle.SensitiveHeaders) > 0 {
		cloned := make(map[string]string, len(bundle.SensitiveHeaders))
		for key, value := range bundle.SensitiveHeaders {
			cloned[key] = value
		}
		bundle.SensitiveHeaders = cloned
	}
	file.AIProviders[strings.TrimSpace(id)] = bundle
	return s.Save(file)
}

func (s *Store) DeleteAIProvider(id string) error {
	file, err := s.Load()
	if err != nil {
		return err
	}
	return s.deleteAIProviderFromFile(file, id)
}

func (s *Store) deleteAIProviderFromFile(file File, id string) error {
	if len(file.AIProviders) != 0 {
		delete(file.AIProviders, strings.TrimSpace(id))
	}
	return s.Save(file)
}
