package services

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

	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/db"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
)

// legacyOrg is the pre-0004 shape of the orgs collection.
//
// Migrations 0001 to 0003 run BEFORE the org-to-instance rename and must keep
// reading and writing org_id in the orgs collection. They deliberately do not
// use shared/models, which has moved on to Instance and instance_id.
type legacyOrg struct {
	OrgID     string    `bson:"org_id"`
	Name      string    `bson:"name"`
	Slug      string    `bson:"slug"`
	CreatedAt time.Time `bson:"created_at"`
}

var backfillCollections = []string{
	"servers", "keys", "assignments", "secrets",
	"workflows", "workflow_steps", "workflow_runs",
	"audit_logs", "monitors", "notification_channels",
	"console_sessions", "incidents", "monitor_rollups",
}

func defaultBackfillOrg(ctx context.Context) (*legacyOrg, error) {
	var org legacyOrg
	err := db.Col("orgs").FindOne(ctx, bson.M{"slug": "default"}).Decode(&org)
	switch {
	case err == nil:
	case errors.Is(err, mongo.ErrNoDocuments):
		org = legacyOrg{OrgID: uuid.NewString(), Name: "Default", Slug: "default", CreatedAt: time.Now()}
		if _, err := db.Col("orgs").InsertOne(ctx, org); err != nil {
			return nil, err
		}
	default:
		return nil, err
	}
	return &org, nil
}

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

	const marker = "0001_default_org_backfill"
	if n, _ := db.Col("migrations").CountDocuments(ctx, bson.M{"_id": marker}); n > 0 {
		return nil
	}

	needs := false
	for _, col := range backfillCollections {
		n, _ := db.Col(col).CountDocuments(ctx, bson.M{"org_id": bson.M{"$exists": false}})
		if n > 0 {
			needs = true
			break
		}
	}

	if needs {
		org, err := defaultBackfillOrg(ctx)
		if err != nil {
			return err
		}
		for _, col := range backfillCollections {
			if _, err := db.Col(col).UpdateMany(ctx,
				bson.M{"org_id": bson.M{"$exists": false}},
				bson.M{"$set": bson.M{"org_id": org.OrgID}},
			); err != nil {
				return err
			}
		}
	}

	_, err := db.Col("migrations").InsertOne(ctx, bson.M{"_id": marker, "applied_at": time.Now()})
	return err
}

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

	const marker = "0003_missed_org_scopes"
	if n, _ := db.Col("migrations").CountDocuments(ctx, bson.M{"_id": marker}); n > 0 {
		return nil
	}

	missed := []string{"audit_logs", "notification_channels"}
	needs := false
	for _, col := range missed {
		n, _ := db.Col(col).CountDocuments(ctx, bson.M{"org_id": bson.M{"$exists": false}})
		if n > 0 {
			needs = true
			break
		}
	}
	if needs {
		org, err := defaultBackfillOrg(ctx)
		if err != nil {
			return err
		}
		for _, col := range missed {
			if _, err := db.Col(col).UpdateMany(ctx,
				bson.M{"org_id": bson.M{"$exists": false}},
				bson.M{"$set": bson.M{"org_id": org.OrgID}},
			); err != nil {
				return err
			}
		}
	}

	if err := backfillOrgFromOwner(ctx, "console_sessions", "server_id", "servers", "server_id"); err != nil {
		return err
	}
	if err := backfillOrgFromOwner(ctx, "incidents", "monitor_id", "monitors", "monitor_id"); err != nil {
		return err
	}
	if err := backfillOrgFromOwner(ctx, "monitor_rollups", "monitor_id", "monitors", "monitor_id"); err != nil {
		return err
	}

	_, err := db.Col("migrations").InsertOne(ctx, bson.M{"_id": marker, "applied_at": time.Now()})
	return err
}

func backfillOrgFromOwner(ctx context.Context, col, localField, ownerCol, ownerField string) error {

	var raw []bson.RawValue
	if err := db.Col(col).Distinct(ctx, localField,
		bson.M{"org_id": bson.M{"$exists": false}}).Decode(&raw); err != nil {
		return err
	}
	for _, rv := range raw {
		id, ok := rv.StringValueOK()
		if !ok || id == "" {
			continue
		}
		var owner struct {
			OrgID string `bson:"org_id"`
		}
		if err := db.Col(ownerCol).FindOne(ctx, bson.M{ownerField: id}).Decode(&owner); err != nil {
			continue
		}
		if owner.OrgID == "" {
			continue
		}
		if _, err := db.Col(col).UpdateMany(ctx,
			bson.M{localField: id, "org_id": bson.M{"$exists": false}},
			bson.M{"$set": bson.M{"org_id": owner.OrgID}},
		); err != nil {
			return err
		}
	}
	return nil
}

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

	const marker = "0002_settings_org_backfill"
	if n, _ := db.Col("migrations").CountDocuments(ctx, bson.M{"_id": marker}); n > 0 {
		return nil
	}

	n, _ := db.Col("settings").CountDocuments(ctx, bson.M{"org_id": bson.M{"$exists": false}})
	if n > 0 {
		var org legacyOrg
		orgCount, err := db.Col("orgs").CountDocuments(ctx, bson.M{})
		if err != nil {
			return err
		}
		switch orgCount {
		case 1:
			if err := db.Col("orgs").FindOne(ctx, bson.M{}).Decode(&org); err != nil {
				return err
			}
		case 0:
			org = legacyOrg{OrgID: uuid.NewString(), Name: "Default", Slug: "default", CreatedAt: time.Now()}
			if _, err := db.Col("orgs").InsertOne(ctx, org); err != nil {
				return err
			}
		default:
			return fmt.Errorf(
				"settings org migration: %d settings document(s) have no org_id but %d orgs exist; "+
					"cannot infer the owner. Set org_id manually on each settings document "+
					"(db.settings.updateOne({_id:<id>},{$set:{org_id:\"<org uuid>\"}})), deleting any "+
					"duplicates, then restart", n, orgCount)
		}
		if org.OrgID != "" {
			if _, err := db.Col("settings").UpdateMany(ctx,
				bson.M{"org_id": bson.M{"$exists": false}},
				bson.M{"$set": bson.M{"org_id": org.OrgID}},
			); err != nil {
				return err
			}
		}
	}

	_, err := db.Col("migrations").InsertOne(ctx, bson.M{"_id": marker, "applied_at": time.Now()})
	return err
}
