package repositories

import (
	"context"
	"fmt"
	"primesys_email/src/db"
	"primesys_email/src/logger"
	"primesys_email/src/models"
	"primesys_email/src/utils"

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

const reportEmailCollection = "report_email_queue"

// after this many failed sends the hourly job stops picking the queue entry
const MaxReportEmailRetries = 3

// InsertReportEmailQueue inserts the queue entry only if one does not already exist
// for the same division and report date, so re-running the cron is safe.
// Returns true when a new entry was inserted.
func InsertReportEmailQueue(ctx context.Context, queue *models.ReportEamilQueue) (bool, error) {

	logger.Info("report date %d", queue.ReportDate)

	filter := bson.M{
		"division_id":    queue.DivisionId,
		"report_date":    queue.ReportDate,
		"device_type_id": queue.DeviceTypeId,
	}

	update := bson.M{
		"$setOnInsert": queue,
	}

	result, err := db.Database().
		Collection(reportEmailCollection).
		UpdateOne(
			ctx,
			filter,
			update,
			options.UpdateOne().SetUpsert(true),
		)

	if err != nil {
		return false, err
	}

	if result.UpsertedCount > 0 {
		return true, nil
	}

	return false, nil
}

func DeleteReportEmailQueueByID(ctx context.Context, id string) error {

	objectId, err := utils.StringToObjectID(id)
	if err != nil {
		return err
	}

	filter := bson.M{
		"_id": objectId,
	}

	result, err := db.Database().
		Collection(reportEmailCollection).
		DeleteOne(ctx, filter)
	if err != nil {
		return err
	}

	if result.DeletedCount == 0 {
		return fmt.Errorf("report email queue document not found")
	}

	logger.Info("Record deleted")

	return nil
}

// DeleteReportEmailQueueByReportDate deletes every queue entry of the report date.
// Returns the number of deleted entries.
func DeleteReportEmailQueueByReportDate(ctx context.Context, reportDate int64) (int64, error) {

	filter := bson.M{
		"report_date": reportDate,
	}

	result, err := db.Database().
		Collection(reportEmailCollection).
		DeleteMany(ctx, filter)
	if err != nil {
		return 0, err
	}

	logger.Info("Deleted %d report email queue entries of report date %d", result.DeletedCount, reportDate)

	return result.DeletedCount, nil
}

// ConvertSentAtToEpoch converts the string sent_at ("2006-01-02 15:04:05" in IST) of every queue
// entry of the report date to an int64 epoch timestamp, a blank sent_at becomes 0.
// Returns the number of converted entries.
func ConvertSentAtToEpoch(ctx context.Context, reportDate int64) (int64, error) {

	filter := bson.M{
		"report_date": reportDate,
		"sent_at":     bson.M{"$type": "string"},
	}

	sentAtDate := bson.M{
		"$dateFromString": bson.M{
			"dateString": "$sent_at",
			"format":     "%Y-%m-%d %H:%M:%S",
			"timezone":   "Asia/Kolkata",
		},
	}

	update := bson.A{
		bson.M{"$set": bson.M{
			"sent_at": bson.M{
				"$cond": bson.A{
					bson.M{"$eq": bson.A{"$sent_at", ""}},
					int64(0),
					bson.M{"$toLong": bson.M{"$divide": bson.A{bson.M{"$toLong": sentAtDate}, 1000}}},
				},
			},
		}},
	}

	result, err := db.Database().
		Collection(reportEmailCollection).
		UpdateMany(ctx, filter, update)
	if err != nil {
		return 0, err
	}

	logger.Info("Converted sent_at of %d report email queue entries of report date %d", result.ModifiedCount, reportDate)

	return result.ModifiedCount, nil
}

// GetUnsentReportEmailQueue returns the queue entries which are not sent yet and whose
// report_extended_time is between startTime and endTime
func GetUnsentReportEmailQueue(ctx context.Context, startTime int64, endTime int64) ([]models.ReportEamilQueue, error) {

	filter := bson.M{
		"report_extended_time": bson.M{
			"$gte": startTime,
			"$lte": endTime,
		},
		"sent": false,
		// $not also matches old entries which have no retry_count yet
		"retry_count": bson.M{
			"$not": bson.M{"$gte": MaxReportEmailRetries},
		},
	}

	cursor, err := db.Database().
		Collection(reportEmailCollection).
		Find(ctx, filter)

	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	queues := make([]models.ReportEamilQueue, 0)
	if err := cursor.All(ctx, &queues); err != nil {
		return nil, err
	}

	return queues, nil
}

// MarkReportEmailQueueProcessing is called when the email sending starts
func MarkReportEmailQueueProcessing(ctx context.Context, id bson.ObjectID) error {
	now := utils.CurrentEpoch()

	return updateReportEmailQueue(ctx, id, bson.M{
		"status":                models.EmailSending,
		"processing_started_at": now,
		"updated_at":            now,
	})
}

// MarkReportEmailQueueSent is called after the emails are sent, the hourly job skips it from now on
func MarkReportEmailQueueSent(ctx context.Context, id bson.ObjectID) error {
	now := utils.CurrentEpoch()

	return updateReportEmailQueue(ctx, id, bson.M{
		"status":     models.EmailSent,
		"sent":       true,
		"sent_at":    now,
		"updated_at": now,
	})
}

// MarkReportEmailQueueFailed keeps sent false and increments retry_count, the next hourly
// run tries again until retry_count reaches MaxReportEmailRetries
func MarkReportEmailQueueFailed(ctx context.Context, id bson.ObjectID, errMsg string) error {
	now := utils.CurrentEpoch()

	return updateReportEmailQueueWith(ctx, id, bson.M{
		"$set": bson.M{
			"status":        models.EmailFailed,
			"error_message": errMsg,
			"error_at":      now,
			"updated_at":    now,
		},
		"$inc": bson.M{
			"retry_count": 1,
		},
	})
}

func updateReportEmailQueue(ctx context.Context, id bson.ObjectID, set bson.M) error {
	return updateReportEmailQueueWith(ctx, id, bson.M{"$set": set})
}

func updateReportEmailQueueWith(ctx context.Context, id bson.ObjectID, update bson.M) error {

	result, err := db.Database().
		Collection(reportEmailCollection).
		UpdateOne(ctx, bson.M{"_id": id}, update)
	if err != nil {
		return err
	}

	if result.MatchedCount == 0 {
		return fmt.Errorf("report email queue %s not found", id.Hex())
	}

	return nil
}
