package services

import (
	"context"
	"fmt"
	"log"
	"time"

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

func GetInstance(instanceID string) (*models.Instance, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	var o models.Instance
	err := db.Col("instances").FindOne(ctx, bson.M{"instance_id": instanceID}).Decode(&o)
	if err != nil {
		return nil, err
	}
	return &o, nil
}

func GetInstanceBySlug(slug string) (*models.Instance, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	var o models.Instance
	err := db.Col("instances").FindOne(ctx, bson.M{"slug": slug}).Decode(&o)
	if err != nil {
		return nil, err
	}
	return &o, nil
}

func ListInstanceIDs() ([]string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cursor, err := db.Col("instances").Find(ctx, bson.M{})
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)
	var orgs []models.Instance
	if err := cursor.All(ctx, &orgs); err != nil {
		return nil, err
	}
	ids := make([]string, 0, len(orgs))
	for _, o := range orgs {
		ids = append(ids, o.InstanceID)
	}
	return ids, nil
}

func CountInstances() (int64, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	return db.Col("instances").CountDocuments(ctx, bson.M{})
}

func FirstInstance() (*models.Instance, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	var o models.Instance
	if err := db.Col("instances").FindOne(ctx, bson.M{}).Decode(&o); err != nil {
		return nil, err
	}
	return &o, nil
}

func AdoptInstance(instanceID, name string) (*models.Instance, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	set := bson.M{"name": name}

	slug := provision.Slugify(name)
	if len(slug) > provision.MaxSlugLength {
		slug = slug[:provision.MaxSlugLength]
	}
	if len(slug) >= provision.MinSlugLength && !provision.ReservedSlugs[slug] {
		n, err := db.Col("instances").CountDocuments(ctx, bson.M{"slug": slug, "instance_id": bson.M{"$ne": instanceID}})
		if err != nil {
			return nil, err
		}
		if n == 0 {
			set["slug"] = slug
		}
	}

	if _, err := db.Col("instances").UpdateOne(ctx, bson.M{"instance_id": instanceID}, bson.M{"$set": set}); err != nil {
		if mongo.IsDuplicateKeyError(err) {
			return nil, fmt.Errorf("instance slug already taken")
		}
		return nil, err
	}
	return GetInstance(instanceID)
}

// CreateInstance creates an organisation and seeds its default workflow steps.
//
// The creation rules live in shared/provision because sitesvc creates
// organisations too. Seeding stays here: shared must not know about workflow
// steps.
func CreateInstance(name string) (*models.Instance, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	o, err := provision.CreateInstance(ctx, db.Database, name)
	if err != nil {
		return nil, err
	}

	if created, updated, err := SeedDefaultSteps(o.InstanceID); err != nil {
		log.Printf("warning: failed to seed default steps for new org %s: %v", o.InstanceID, err)
	} else {
		log.Printf("default steps seeded for new org %s: %d created, %d updated", o.InstanceID, created, updated)
	}
	return o, nil
}
