package repository

import (
	"context"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	"haixun-backend/internal/model/content_ops/domain/entity"
	domrepo "haixun-backend/internal/model/content_ops/domain/repository"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type mongoRepository struct {
	topics   *mongo.Collection
	plans    *mongo.Collection
	feedback *mongo.Collection
	sources  *mongo.Collection
	chunks   *mongo.Collection
	formulas *mongo.Collection
}

func NewMongoRepository(db *mongo.Database) domrepo.Repository {
	if db == nil {
		return &mongoRepository{}
	}
	return &mongoRepository{
		topics:   db.Collection(entity.TopicCandidateCollectionName),
		plans:    db.Collection(entity.ContentPlanCollectionName),
		feedback: db.Collection(entity.FeedbackEventCollectionName),
		sources:  db.Collection(entity.KnowledgeSourceCollectionName),
		chunks:   db.Collection(entity.KnowledgeChunkCollectionName),
		formulas: db.Collection(entity.FormulaPoolCollectionName),
	}
}

func (r *mongoRepository) EnsureIndexes(ctx context.Context) error {
	if r.topics == nil || r.plans == nil || r.feedback == nil || r.sources == nil || r.chunks == nil || r.formulas == nil {
		return nil
	}
	if _, err := r.topics.Indexes().CreateMany(ctx, []mongo.IndexModel{{Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "persona_id", Value: 1}, {Key: "create_at", Value: -1}}}}); err != nil {
		return err
	}
	if _, err := r.plans.Indexes().CreateMany(ctx, []mongo.IndexModel{{Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "persona_id", Value: 1}, {Key: "create_at", Value: -1}}}, {Keys: bson.D{{Key: "topic_candidate_id", Value: 1}}}}); err != nil {
		return err
	}
	_, err := r.feedback.Indexes().CreateMany(ctx, []mongo.IndexModel{{Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "persona_id", Value: 1}, {Key: "create_at", Value: -1}}}, {Keys: bson.D{{Key: "content_plan_id", Value: 1}}}, {Keys: bson.D{{Key: "draft_id", Value: 1}}}})
	if err != nil {
		return err
	}
	if _, err := r.sources.Indexes().CreateMany(ctx, []mongo.IndexModel{{Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "persona_id", Value: 1}, {Key: "create_at", Value: -1}}}}); err != nil {
		return err
	}
	if _, err := r.chunks.Indexes().CreateMany(ctx, []mongo.IndexModel{{Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "persona_id", Value: 1}, {Key: "create_at", Value: -1}}}, {Keys: bson.D{{Key: "source_id", Value: 1}}}}); err != nil {
		return err
	}
	_, err = r.formulas.Indexes().CreateMany(ctx, []mongo.IndexModel{{Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "persona_id", Value: 1}, {Key: "type", Value: 1}}}})
	return err
}

func personaFilter(tenantID, ownerUID, personaID string) bson.M {
	return bson.M{"tenant_id": strings.TrimSpace(tenantID), "owner_uid": strings.TrimSpace(ownerUID), "persona_id": strings.TrimSpace(personaID)}
}

func normalizeLimit(limit int) int64 {
	if limit <= 0 {
		limit = 50
	}
	if limit > 200 {
		limit = 200
	}
	return int64(limit)
}

func (r *mongoRepository) CreateTopicCandidate(ctx context.Context, item *entity.TopicCandidate) error {
	if r.topics == nil {
		return app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	_, err := r.topics.InsertOne(ctx, item)
	return err
}

func (r *mongoRepository) ListTopicCandidates(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]entity.TopicCandidate, error) {
	if r.topics == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	cur, err := r.topics.Find(ctx, personaFilter(tenantID, ownerUID, personaID), options.Find().SetSort(bson.D{{Key: "create_at", Value: -1}}).SetLimit(normalizeLimit(limit)))
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	var out []entity.TopicCandidate
	return out, cur.All(ctx, &out)
}

func (r *mongoRepository) CreateContentPlan(ctx context.Context, item *entity.ContentPlan) error {
	if r.plans == nil {
		return app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	_, err := r.plans.InsertOne(ctx, item)
	return err
}

func (r *mongoRepository) UpdateContentPlan(ctx context.Context, tenantID, ownerUID, personaID, id string, patch map[string]any) (*entity.ContentPlan, error) {
	if r.plans == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	filter := personaFilter(tenantID, ownerUID, personaID)
	filter["_id"] = strings.TrimSpace(id)
	var out entity.ContentPlan
	err := r.plans.FindOneAndUpdate(ctx, filter, bson.M{"$set": patch}, options.FindOneAndUpdate().SetReturnDocument(options.After)).Decode(&out)
	if err == mongo.ErrNoDocuments {
		return nil, app.For(code.Persona).ResNotFound("找不到內容計畫")
	}
	return &out, err
}

func (r *mongoRepository) GetContentPlan(ctx context.Context, tenantID, ownerUID, personaID, id string) (*entity.ContentPlan, error) {
	if r.plans == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	filter := personaFilter(tenantID, ownerUID, personaID)
	filter["_id"] = strings.TrimSpace(id)
	var out entity.ContentPlan
	err := r.plans.FindOne(ctx, filter).Decode(&out)
	if err == mongo.ErrNoDocuments {
		return nil, app.For(code.Persona).ResNotFound("找不到內容計畫")
	}
	return &out, err
}

func (r *mongoRepository) ListContentPlans(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]entity.ContentPlan, error) {
	if r.plans == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	cur, err := r.plans.Find(ctx, personaFilter(tenantID, ownerUID, personaID), options.Find().SetSort(bson.D{{Key: "create_at", Value: -1}}).SetLimit(normalizeLimit(limit)))
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	var out []entity.ContentPlan
	return out, cur.All(ctx, &out)
}

func (r *mongoRepository) CreateFeedbackEvent(ctx context.Context, item *entity.FeedbackEvent) error {
	if r.feedback == nil {
		return app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	_, err := r.feedback.InsertOne(ctx, item)
	return err
}

func (r *mongoRepository) ListFeedbackEvents(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]entity.FeedbackEvent, error) {
	if r.feedback == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	cur, err := r.feedback.Find(ctx, personaFilter(tenantID, ownerUID, personaID), options.Find().SetSort(bson.D{{Key: "create_at", Value: -1}}).SetLimit(normalizeLimit(limit)))
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	var out []entity.FeedbackEvent
	return out, cur.All(ctx, &out)
}

func (r *mongoRepository) CreateKnowledgeSource(ctx context.Context, item *entity.KnowledgeSource, chunks []*entity.KnowledgeChunk) error {
	if r.sources == nil || r.chunks == nil {
		return app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	if _, err := r.sources.InsertOne(ctx, item); err != nil {
		return err
	}
	if len(chunks) == 0 {
		return nil
	}
	docs := make([]any, 0, len(chunks))
	for _, c := range chunks {
		docs = append(docs, c)
	}
	_, err := r.chunks.InsertMany(ctx, docs)
	return err
}

func (r *mongoRepository) ListKnowledgeSources(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]entity.KnowledgeSource, error) {
	if r.sources == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	cur, err := r.sources.Find(ctx, personaFilter(tenantID, ownerUID, personaID), options.Find().SetSort(bson.D{{Key: "create_at", Value: -1}}).SetLimit(normalizeLimit(limit)))
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	var out []entity.KnowledgeSource
	return out, cur.All(ctx, &out)
}

func (r *mongoRepository) ListKnowledgeChunks(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]entity.KnowledgeChunk, error) {
	if r.chunks == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	cur, err := r.chunks.Find(ctx, personaFilter(tenantID, ownerUID, personaID), options.Find().SetSort(bson.D{{Key: "create_at", Value: -1}}).SetLimit(normalizeLimit(limit)))
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	var out []entity.KnowledgeChunk
	return out, cur.All(ctx, &out)
}

func (r *mongoRepository) CreateFormulaPool(ctx context.Context, item *entity.FormulaPool) error {
	if r.formulas == nil {
		return app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	_, err := r.formulas.InsertOne(ctx, item)
	return err
}

func (r *mongoRepository) ListFormulaPools(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]entity.FormulaPool, error) {
	if r.formulas == nil {
		return nil, app.For(code.Persona).DBUnavailable("Mongo is not configured")
	}
	cur, err := r.formulas.Find(ctx, personaFilter(tenantID, ownerUID, personaID), options.Find().SetSort(bson.D{{Key: "weight", Value: -1}, {Key: "create_at", Value: -1}}).SetLimit(normalizeLimit(limit)))
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)
	var out []entity.FormulaPool
	return out, cur.All(ctx, &out)
}
