package repository

import (
	"context"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"google.golang.org/grpc/codes"
	"noteservice/api/noteservice"
	"noteservice/internal/model"
	"noteservice/internal/persistance/database"
	"noteservice/internal/tools"
)

const (
	NoteCollection = "notes"
)

type NoteRepository struct {
	database *database.Database
}

func (n *NoteRepository) GetNote(context context.Context, id primitive.ObjectID) (*noteservice.Note, error) {
	noteCollection := n.database.Client.Collection(NoteCollection)

	filter := bson.M{"_id": id}

	result := noteCollection.FindOne(context, filter)
	if result.Err() == mongo.ErrNoDocuments {
		return nil, tools.ConvertError(codes.NotFound, "Note with provided id not found", result.Err())
	}

	if result.Err() != nil {
		return nil, tools.ConvertError(codes.Internal, "Error on query the database", result.Err())
	}

	note := &model.Note{}
	err := result.Decode(note)
	if err != nil {
		return nil, tools.ConvertError(codes.Internal, "Can`t decode note", err)
	}
	return note.ConvertToServiceNote(), nil
}

func (n *NoteRepository) GetNotes(context context.Context, request *noteservice.NoteRequest) (*noteservice.NoteResponse, error) {
	noteCollection := n.database.Client.Collection(NoteCollection)

	oids := make([]primitive.ObjectID, len(request.Ids))
	for i := range request.Ids {
		obId, err := primitive.ObjectIDFromHex(request.Ids[i])
		if err != nil {
			return nil, tools.ConvertError(codes.InvalidArgument, fmt.Sprintf("Invalid id format %s", request.Ids[i]), err)
		}
		oids[i] = obId
	}
	filter := bson.M{"_id": bson.M{"$in": oids}}
	cursor, err := noteCollection.Find(context, filter)
	if err != nil {
		return nil, tools.ConvertError(codes.Internal, "Database error", err)
	}

	if cursor == nil {
		return nil, tools.CreateError(codes.NotFound, "No note correspond to provided id", "Not found")
	}

	var result []*noteservice.Note
	for cursor.Next(context) {
		note := &model.Note{}
		err := cursor.Decode(note)
		if err != nil {
			return nil, tools.ConvertError(codes.Internal, "Can`t decode note", err)
		}
		serviceNote := note.ConvertToServiceNote()
		result = append(result, serviceNote)
	}
	return &noteservice.NoteResponse{
		Notes: result,
	}, nil
}

func (n *NoteRepository) CreateNote(context context.Context, request *noteservice.NoteOperationRequest) (*noteservice.Note, error) {
	noteCollection := n.database.Client.Collection(NoteCollection)

	note := model.Note{
		Title:      request.Note.Title,
		Body:       request.Note.Body,
		Categories: request.Note.Categories,
		Encrypted:  request.Note.Encrypted,
		Alias:      request.Note.Alias,
	}
	result, err := noteCollection.InsertOne(context, note)
	if err != nil {
		return nil, tools.ConvertError(codes.Internal, "Error on inserting into database", err)
	}
	noteId := result.InsertedID.(primitive.ObjectID)
	insertedNote, err := n.GetNote(context, noteId)
	if err != nil {
		return nil, err
	}

	return insertedNote, nil
}

func (n *NoteRepository) UpdateNote(context context.Context, request *noteservice.NoteOperationRequest) (*noteservice.Note, error) {
	noteCollection := n.database.Client.Collection(NoteCollection)
	noteId, err := primitive.ObjectIDFromHex(request.Note.Id)
	if err != nil {
		return nil, tools.ConvertError(codes.InvalidArgument, "Invalid format of note id", err)
	}
	_, err = n.GetNote(context, noteId)
	if err != nil {
		return nil, err
	}
	update := bson.M{
		"$set": model.Note{
			Title:      request.Note.Title,
			Body:       request.Note.Body,
			Categories: request.Note.Categories,
			Encrypted:  request.Note.Encrypted,
			Alias:      request.Note.Alias,
		},
	}
	filter := bson.M{"_id": noteId}
	_, err = noteCollection.UpdateOne(context, filter, update)
	if err != nil {
		return nil, tools.ConvertError(codes.Internal, "Error on note update", err)
	}

	return n.GetNote(context, noteId)
}

func (n *NoteRepository) DeleteNote(context context.Context, request *noteservice.DeleteRequest) (*noteservice.Response, error) {
	noteCollection := n.database.Client.Collection(NoteCollection)
	noteId, err := primitive.ObjectIDFromHex(request.Id)
	if err != nil {
		return nil, tools.ConvertError(codes.InvalidArgument, "Invalid format of note id", err)
	}
	filter := bson.M{"_id": noteId}

	result, err := noteCollection.DeleteOne(context, filter)
	if err != nil {
		return nil, tools.ConvertError(codes.Internal, "Error on note update", err)
	}

	if result.DeletedCount == 0 {
		return nil, tools.CreateError(codes.NotFound, "Note with provided id not found", "Note not found")
	}

	return &noteservice.Response{}, nil
}
