package services

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"time"

	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/db"
	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

func EnsureSecretIndexes() error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := db.Col("secrets").Indexes().DropOne(ctx, "group_1_key_1"); err != nil && !isIndexNotFound(err) {
		return err
	}

	_, err := db.Col("secrets").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "instance_id", Value: 1}, {Key: "group", Value: 1}, {Key: "key", Value: 1}},
		Options: options.Index().SetUnique(true),
	})
	return err
}

func isIndexNotFound(err error) bool {
	var ce mongo.CommandError
	if errors.As(err, &ce) {
		return ce.Code == 27 || ce.Name == "IndexNotFound" ||
			ce.Code == 26 || ce.Name == "NamespaceNotFound"
	}
	return false
}

func ListSecretGroups(instanceID string) ([]models.GroupSummary, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	pipeline := mongo.Pipeline{
		{{Key: "$match", Value: bson.D{{Key: "instance_id", Value: instanceID}}}},
		{{Key: "$group", Value: bson.D{
			{Key: "_id", Value: "$group"},
			{Key: "key_count", Value: bson.D{{Key: "$sum", Value: 1}}},
			{Key: "updated_at", Value: bson.D{{Key: "$max", Value: "$updated_at"}}},
		}}},
		{{Key: "$sort", Value: bson.D{{Key: "_id", Value: 1}}}},
	}

	cursor, err := db.Col("secrets").Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	var rows []struct {
		Group     string    `bson:"_id"`
		KeyCount  int       `bson:"key_count"`
		UpdatedAt time.Time `bson:"updated_at"`
	}
	if err := cursor.All(ctx, &rows); err != nil {
		return nil, err
	}

	groups := make([]models.GroupSummary, 0, len(rows))
	for _, r := range rows {
		groups = append(groups, models.GroupSummary{
			Group:     r.Group,
			KeyCount:  r.KeyCount,
			UpdatedAt: r.UpdatedAt,
		})
	}
	return groups, nil
}

func GetSecretGroup(instanceID, group string) ([]models.Secret, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	cursor, err := db.Col("secrets").Find(ctx, bson.M{"instance_id": instanceID, "group": group},
		options.Find().SetSort(bson.D{{Key: "key", Value: 1}}))
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	var docs []models.Secret
	if err := cursor.All(ctx, &docs); err != nil {
		return nil, err
	}
	return docs, nil
}

func GetSecretGroupDecrypted(instanceID, group string) (map[string]string, error) {
	docs, err := GetSecretGroup(instanceID, group)
	if err != nil {
		return nil, err
	}
	result := make(map[string]string, len(docs))
	for _, doc := range docs {
		val, err := decryptString(doc.EncryptedValue)
		if err != nil {
			return nil, fmt.Errorf("decrypt %s/%s: %w", group, doc.Key, err)
		}
		result[doc.Key] = val
	}
	return result, nil
}

func RevealSecret(instanceID, group, key string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var doc models.Secret
	err := db.Col("secrets").FindOne(ctx, bson.M{"instance_id": instanceID, "group": group, "key": key}).Decode(&doc)
	if err == mongo.ErrNoDocuments {
		return "", fmt.Errorf("secret not found")
	}
	if err != nil {
		return "", err
	}
	return decryptString(doc.EncryptedValue)
}

func UpsertSecrets(instanceID, group string, values map[string]string) error {
	if err := CheckSecretGroupLimit(instanceID, group); err != nil {
		return err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	for key, val := range values {
		encrypted, err := encryptString(val)
		if err != nil {
			return fmt.Errorf("encrypt %s: %w", key, err)
		}
		_, err = db.Col("secrets").UpdateOne(ctx,
			bson.M{"instance_id": instanceID, "group": group, "key": key},
			bson.M{"$set": bson.M{
				"instance_id":     instanceID,
				"encrypted_value": encrypted,
				"updated_at":      time.Now(),
			}},
			options.UpdateOne().SetUpsert(true),
		)
		if err != nil {
			return err
		}
	}
	return nil
}

func SortedKeys(m map[string]string) []string {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	return keys
}

func DeleteSecret(instanceID, group, key string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_, err := db.Col("secrets").DeleteOne(ctx, bson.M{"instance_id": instanceID, "group": group, "key": key})
	return err
}

func DeleteSecretGroup(instanceID, group string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_, err := db.Col("secrets").DeleteMany(ctx, bson.M{"instance_id": instanceID, "group": group})
	return err
}
