package app

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

	"GoNavi-Wails/internal/appdata"
	"GoNavi-Wails/internal/connection"
	"GoNavi-Wails/internal/dailysecret"
	"GoNavi-Wails/internal/secretstore"
	"github.com/google/uuid"
)

const (
	savedConnectionsFileName  = "connections.json"
	savedConnectionSecretKind = "connection"
)

type connectionSecretBundle 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"`
}

type savedConnectionsFile struct {
	Connections []connection.SavedConnectionView `json:"connections"`
}

type savedConnectionRepository struct {
	configDir   string
	secretStore secretstore.SecretStore
}

func resolveAppConfigDir() string {
	return appdata.MustResolveActiveRoot()
}

func newSavedConnectionRepository(configDir string, store secretstore.SecretStore) *savedConnectionRepository {
	if strings.TrimSpace(configDir) == "" {
		configDir = resolveAppConfigDir()
	}
	if store == nil {
		store = secretstore.NewUnavailableStore("secret store unavailable")
	}
	return &savedConnectionRepository{configDir: configDir, secretStore: store}
}

func (b connectionSecretBundle) 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) != ""
}

func mergeConnectionSecretBundles(base, overlay connectionSecretBundle) connectionSecretBundle {
	merged := base
	if strings.TrimSpace(overlay.Password) != "" {
		merged.Password = overlay.Password
	}
	if strings.TrimSpace(overlay.SSHPassword) != "" {
		merged.SSHPassword = overlay.SSHPassword
	}
	if strings.TrimSpace(overlay.ProxyPassword) != "" {
		merged.ProxyPassword = overlay.ProxyPassword
	}
	if strings.TrimSpace(overlay.HTTPTunnelPassword) != "" {
		merged.HTTPTunnelPassword = overlay.HTTPTunnelPassword
	}
	if strings.TrimSpace(overlay.MySQLReplicaPassword) != "" {
		merged.MySQLReplicaPassword = overlay.MySQLReplicaPassword
	}
	if strings.TrimSpace(overlay.MongoReplicaPassword) != "" {
		merged.MongoReplicaPassword = overlay.MongoReplicaPassword
	}
	if strings.TrimSpace(overlay.OpaqueURI) != "" {
		merged.OpaqueURI = overlay.OpaqueURI
	}
	if strings.TrimSpace(overlay.OpaqueDSN) != "" {
		merged.OpaqueDSN = overlay.OpaqueDSN
	}
	return merged
}

func applyConnectionSecretClears(bundle connectionSecretBundle, input connection.SavedConnectionInput) connectionSecretBundle {
	cleared := bundle
	if input.ClearPrimaryPassword {
		cleared.Password = ""
	}
	if input.ClearSSHPassword {
		cleared.SSHPassword = ""
	}
	if input.ClearProxyPassword {
		cleared.ProxyPassword = ""
	}
	if input.ClearHTTPTunnelPassword {
		cleared.HTTPTunnelPassword = ""
	}
	if input.ClearMySQLReplicaPassword {
		cleared.MySQLReplicaPassword = ""
	}
	if input.ClearMongoReplicaPassword {
		cleared.MongoReplicaPassword = ""
	}
	if input.ClearOpaqueURI {
		cleared.OpaqueURI = ""
	}
	if input.ClearOpaqueDSN {
		cleared.OpaqueDSN = ""
	}
	return cleared
}

func cloneStringSlice(input []string) []string {
	if len(input) == 0 {
		return nil
	}
	cloned := make([]string, len(input))
	copy(cloned, input)
	return cloned
}

func cloneIntSlice(input []int) []int {
	if len(input) == 0 {
		return nil
	}
	cloned := make([]int, len(input))
	copy(cloned, input)
	return cloned
}

func splitConnectionSecrets(input connection.SavedConnectionInput) (connection.SavedConnectionView, connectionSecretBundle) {
	id := strings.TrimSpace(input.ID)
	if id == "" {
		id = strings.TrimSpace(input.Config.ID)
	}

	meta := input.Config
	meta.ID = id
	meta.SavePassword = false

	bundle := extractConnectionSecretBundle(meta)
	meta = stripConnectionSecretFields(meta)

	view := connection.SavedConnectionView{
		ID:                      id,
		Name:                    strings.TrimSpace(input.Name),
		Config:                  meta,
		IncludeDatabases:        cloneStringSlice(input.IncludeDatabases),
		IncludeRedisDatabases:   cloneIntSlice(input.IncludeRedisDatabases),
		IconType:                strings.TrimSpace(input.IconType),
		IconColor:               strings.TrimSpace(input.IconColor),
		HasPrimaryPassword:      strings.TrimSpace(bundle.Password) != "",
		HasSSHPassword:          strings.TrimSpace(bundle.SSHPassword) != "",
		HasProxyPassword:        strings.TrimSpace(bundle.ProxyPassword) != "",
		HasHTTPTunnelPassword:   strings.TrimSpace(bundle.HTTPTunnelPassword) != "",
		HasMySQLReplicaPassword: strings.TrimSpace(bundle.MySQLReplicaPassword) != "",
		HasMongoReplicaPassword: strings.TrimSpace(bundle.MongoReplicaPassword) != "",
		HasOpaqueURI:            strings.TrimSpace(bundle.OpaqueURI) != "",
		HasOpaqueDSN:            strings.TrimSpace(bundle.OpaqueDSN) != "",
	}
	return view, bundle
}

func (r *savedConnectionRepository) connectionsPath() string {
	return filepath.Join(r.configDir, savedConnectionsFileName)
}

func (r *savedConnectionRepository) dailySecrets() *dailysecret.Store {
	return dailysecret.NewStore(r.configDir)
}

func (r *savedConnectionRepository) load() ([]connection.SavedConnectionView, error) {
	data, err := os.ReadFile(r.connectionsPath())
	if err != nil {
		if os.IsNotExist(err) {
			return []connection.SavedConnectionView{}, nil
		}
		return nil, err
	}

	var file savedConnectionsFile
	if err := json.Unmarshal(data, &file); err != nil {
		return nil, err
	}
	if file.Connections == nil {
		return []connection.SavedConnectionView{}, nil
	}
	return file.Connections, nil
}

func (r *savedConnectionRepository) saveAll(connections []connection.SavedConnectionView) error {
	if err := os.MkdirAll(r.configDir, 0o755); err != nil {
		return err
	}
	payload, err := json.MarshalIndent(savedConnectionsFile{Connections: connections}, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(r.connectionsPath(), payload, 0o644)
}

func (r *savedConnectionRepository) Save(input connection.SavedConnectionInput) (connection.SavedConnectionView, error) {
	if strings.TrimSpace(input.ID) == "" && strings.TrimSpace(input.Config.ID) == "" {
		input.ID = "conn-" + uuid.New().String()[:8]
	}
	if strings.TrimSpace(input.ID) == "" {
		input.ID = strings.TrimSpace(input.Config.ID)
	}
	input.Config.ID = input.ID

	connections, err := r.load()
	if err != nil {
		return connection.SavedConnectionView{}, err
	}

	view, bundle := splitConnectionSecrets(input)
	index := -1
	var existing connection.SavedConnectionView
	for i, item := range connections {
		if item.ID == view.ID {
			index = i
			existing = item
			break
		}
	}

	mergedBundle := bundle
	if index >= 0 && savedConnectionViewHasSecrets(existing) {
		existingBundle, bundleErr := r.loadSecretBundle(existing)
		if bundleErr != nil {
			return connection.SavedConnectionView{}, bundleErr
		}
		mergedBundle = mergeConnectionSecretBundles(existingBundle, bundle)
	}
	mergedBundle = applyConnectionSecretClears(mergedBundle, input)

	if mergedBundle.hasAny() {
		if storeErr := r.saveSecretBundle(view.ID, mergedBundle); storeErr != nil {
			return connection.SavedConnectionView{}, storeErr
		}
	} else {
		if deleteErr := r.deleteSecretBundle(view.ID); deleteErr != nil {
			return connection.SavedConnectionView{}, deleteErr
		}
	}
	view.SecretRef = ""
	applyConnectionBundleFlags(&view, mergedBundle)

	if index >= 0 {
		connections[index] = view
	} else {
		connections = append(connections, view)
	}
	if err := r.saveAll(connections); err != nil {
		return connection.SavedConnectionView{}, err
	}
	return view, nil
}

func (r *savedConnectionRepository) Find(id string) (connection.SavedConnectionView, error) {
	connections, err := r.load()
	if err != nil {
		return connection.SavedConnectionView{}, err
	}
	for _, item := range connections {
		if item.ID == strings.TrimSpace(id) {
			return item, nil
		}
	}
	return connection.SavedConnectionView{}, fmt.Errorf("saved connection not found: %s", id)
}

func (r *savedConnectionRepository) saveSecretBundle(id string, bundle connectionSecretBundle) error {
	return r.dailySecrets().PutConnection(id, toDailyConnectionBundle(bundle))
}

func (r *savedConnectionRepository) deleteSecretBundle(id string) error {
	return r.dailySecrets().DeleteConnection(id)
}

func (r *savedConnectionRepository) storeSecretBundle(id string, existingRef string, bundle connectionSecretBundle) (string, error) {
	if r.secretStore == nil {
		return "", fmt.Errorf("secret store unavailable")
	}
	if err := r.secretStore.HealthCheck(); err != nil {
		return "", err
	}
	ref := strings.TrimSpace(existingRef)
	if ref == "" {
		var err error
		ref, err = secretstore.BuildRef(savedConnectionSecretKind, id)
		if err != nil {
			return "", err
		}
	}
	payload, err := json.Marshal(bundle)
	if err != nil {
		return "", err
	}
	if err := r.secretStore.Put(ref, payload); err != nil {
		return "", err
	}
	return ref, nil
}

func (r *savedConnectionRepository) loadSecretBundle(view connection.SavedConnectionView) (connectionSecretBundle, error) {
	inline := extractConnectionSecretBundle(view.Config)
	if inline.hasAny() {
		return inline, nil
	}
	if !savedConnectionViewHasSecrets(view) {
		return connectionSecretBundle{}, nil
	}
	bundle, ok, err := r.dailySecrets().GetConnection(view.ID)
	if err != nil {
		return connectionSecretBundle{}, err
	}
	if ok {
		return fromDailyConnectionBundle(bundle), nil
	}
	return connectionSecretBundle{}, os.ErrNotExist
}

func (r *savedConnectionRepository) loadSecretBundleFromStore(view connection.SavedConnectionView) (connectionSecretBundle, error) {
	if r.secretStore == nil {
		return connectionSecretBundle{}, fmt.Errorf("secret store unavailable")
	}
	ref := strings.TrimSpace(view.SecretRef)
	if ref == "" {
		var err error
		ref, err = secretstore.BuildRef(savedConnectionSecretKind, view.ID)
		if err != nil {
			return connectionSecretBundle{}, err
		}
	}
	payload, err := r.secretStore.Get(ref)
	if err != nil {
		return connectionSecretBundle{}, err
	}
	var bundle connectionSecretBundle
	if err := json.Unmarshal(payload, &bundle); err != nil {
		return connectionSecretBundle{}, err
	}
	return bundle, nil
}

func savedConnectionViewHasSecrets(view connection.SavedConnectionView) bool {
	return view.HasPrimaryPassword || view.HasSSHPassword || view.HasProxyPassword || view.HasHTTPTunnelPassword ||
		view.HasMySQLReplicaPassword || view.HasMongoReplicaPassword || view.HasOpaqueURI || view.HasOpaqueDSN
}

func applyConnectionBundleFlags(view *connection.SavedConnectionView, bundle connectionSecretBundle) {
	view.HasPrimaryPassword = strings.TrimSpace(bundle.Password) != ""
	view.HasSSHPassword = strings.TrimSpace(bundle.SSHPassword) != ""
	view.HasProxyPassword = strings.TrimSpace(bundle.ProxyPassword) != ""
	view.HasHTTPTunnelPassword = strings.TrimSpace(bundle.HTTPTunnelPassword) != ""
	view.HasMySQLReplicaPassword = strings.TrimSpace(bundle.MySQLReplicaPassword) != ""
	view.HasMongoReplicaPassword = strings.TrimSpace(bundle.MongoReplicaPassword) != ""
	view.HasOpaqueURI = strings.TrimSpace(bundle.OpaqueURI) != ""
	view.HasOpaqueDSN = strings.TrimSpace(bundle.OpaqueDSN) != ""
}

func buildDuplicateConnectionName(baseName string, existing []connection.SavedConnectionView) string {
	trimmedBaseName := strings.TrimSpace(baseName)
	if trimmedBaseName == "" {
		trimmedBaseName = "连接"
	}
	suffix := " - 副本"
	usedNames := make(map[string]struct{}, len(existing))
	for _, item := range existing {
		usedNames[strings.TrimSpace(item.Name)] = struct{}{}
	}
	candidate := trimmedBaseName + suffix
	counter := 2
	for {
		if _, exists := usedNames[candidate]; !exists {
			return candidate
		}
		candidate = fmt.Sprintf("%s%s %d", trimmedBaseName, suffix, counter)
		counter++
	}
}

func (r *savedConnectionRepository) List() ([]connection.SavedConnectionView, error) {
	return r.load()
}

func (r *savedConnectionRepository) Delete(id string) error {
	connections, err := r.load()
	if err != nil {
		return err
	}
	filtered := make([]connection.SavedConnectionView, 0, len(connections))
	for _, item := range connections {
		if item.ID == strings.TrimSpace(id) {
			if deleteErr := r.deleteSecretBundle(item.ID); deleteErr != nil {
				return deleteErr
			}
			continue
		}
		filtered = append(filtered, item)
	}
	return r.saveAll(filtered)
}

func (r *savedConnectionRepository) Duplicate(id string) (connection.SavedConnectionView, error) {
	connections, err := r.load()
	if err != nil {
		return connection.SavedConnectionView{}, err
	}

	index := -1
	for i, item := range connections {
		if item.ID == strings.TrimSpace(id) {
			index = i
			break
		}
	}
	if index < 0 {
		return connection.SavedConnectionView{}, fmt.Errorf("saved connection not found: %s", id)
	}

	original := connections[index]
	duplicate := original
	duplicate.ID = "conn-" + uuid.New().String()[:8]
	duplicate.Config.ID = duplicate.ID
	duplicate.Name = buildDuplicateConnectionName(original.Name, connections)

	bundle, err := r.loadSecretBundle(original)
	if err != nil {
		return connection.SavedConnectionView{}, err
	}
	if bundle.hasAny() {
		if storeErr := r.saveSecretBundle(duplicate.ID, bundle); storeErr != nil {
			return connection.SavedConnectionView{}, storeErr
		}
	}
	duplicate.SecretRef = ""
	applyConnectionBundleFlags(&duplicate, bundle)

	connections = append(connections, duplicate)
	if err := r.saveAll(connections); err != nil {
		return connection.SavedConnectionView{}, err
	}
	return duplicate, nil
}
