/*
DESCRIPTION:
Any functions that are available to be used externally.
*/
package crud

import (
	"fmt"
	"strconv"

	dg "github.com/karincake/apem/db-gorm-pg"
	d "github.com/karincake/dodol"

	pl "simrs-vx/pkg/logger"

	"gorm.io/gorm"

	e "simrs-vx/internal/domain/_template/single"
)

const source = "crud"

func Create(input e.Createdto) (*d.Data, error) {
	data := e.Single{}

	setData(input, &data)

	event := pl.Event{
		Feature: "Create",
		Source:  source,
	}

	// Start log
	event.Action = "Create"
	event.Status = "started"
	pl.SetLogInfo(event, input)
	err := dg.I.Transaction(func(tx *gorm.DB) error {
		for i := range createPreMw {
			mwName := fmt.Sprintf("createPreMw[%d]", i)

			event.Action = mwName
			event.Status = "started"
			pl.SetLogInfo(event, data)

			if err := createPreMw[i](&input, &data, tx); err != nil {
				event.Status = "failed"
				event.ErrInfo = pl.ErrorInfo{
					Code:   "MW_PRE_FAILED", // TODO: add to lang json
					Detail: fmt.Sprintf("Pre-middleware %s failed", mwName),
					Raw:    err,
				}
				return pl.SetLogError(event, data)
			}

			event.Status = "completed"
			pl.SetLogInfo(event, nil)
		}

		event.Action = "DBCreate"
		event.Status = "started"
		pl.SetLogInfo(event, data)
		result, err := CreateData(&data, tx)
		if err != nil {
			event.Status = "failed"
			event.ErrInfo = pl.ErrorInfo{
				Code:   "data-create-fail",
				Detail: "Database insert failed",
				Raw:    err,
			}
			return pl.SetLogError(event, data)
		}

		event.Status = "completed"
		pl.SetLogInfo(event, nil)

		for i := range createPostMw {
			mwName := fmt.Sprintf("createPostMw[%d]", i)

			event.Action = mwName
			event.Status = "started"
			pl.SetLogInfo(event, input)
			if err := createPostMw[i](&input, &data, tx); err != nil {
				event.Status = "failed"
				event.ErrInfo = pl.ErrorInfo{
					Code:   "MW_POST_FAILED", // TODO: add to lang json
					Detail: fmt.Sprintf("Post-middleware %s failed", mwName),
					Raw:    err,
				}
				return pl.SetLogError(event, data)
			}
		}

		event.Status = "completed"
		pl.SetLogInfo(event, nil)

		return nil
	})

	return &d.Data{
		Meta: d.II{
			"source": source,
			"type":   "list",
			"status": "created",
		},
		Data: result,
	}, nil
}

func ReadList(input e.ReadListDto) (*d.Data, error) {
	err := dg.I.Transaction(func(tx *gorm.DB) error {
		for i := range readListPreMw {
			if err := readListPreMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}

		data, meta, err := ReadListData(input, tx)
		if err != nil {
			return nil, err
		}

		for i := range readListPostMw {
			if err := readListPostMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}

		return nil
	})

	return &d.Data{
		Meta: d.IS{
			"source":            source,
			"structure":         "list-data",
			"status":            "fetched",
			"page_number":       strconv.Itoa(meta.PageNumber),
			"page_size":         strconv.Itoa(meta.PageSize),
			"record_totalCount": strconv.Itoa(meta.Count),
		},
		Data: data,
	}, nil
}

func ReadDetail(input e.ReadDetailDto) (*d.Data, error) {
	err := dg.I.Transaction(func(tx *gorm.DB) error {
		for i := range readDetailPreMw {
			if err := readDetailPreMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}
		data, err := ReadDetailData(input, tx)
		if err != nil {
			return nil, err
		}
		for i := range readDetailPostMw {
			if err := readDetailPostMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}
		return nil
	})
	return &d.Data{
		Meta: d.IS{
			"source":    source,
			"structure": "single-data",
			"status":    "fetched",
		},
		Data: data,
	}, nil
}

func Update(input e.Updatedto) (*d.Data, error) {
	err := dg.I.Transaction(func(tx *gorm.DB) error {
		data, err := ReadDetailData(input, tx)
		if err != nil {
			return nil
		}

		setData(&data, &input)
		for i := range updatePreMw {
			if err := updatePreMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}
		if err := UpdateData(*data, tx); err != nil {
			return nil
		}
		for i := range updatePostMw {
			if err := updatePostMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}
		return nil
	})
	return &d.Data{
		Meta: d.IS{
			"source":    source,
			"structure": "single-data",
			"status":    "updated",
		},
		Data: data,
	}, nil

}

func Delete(input e.Deletedto) (*d.Data, error) {
	err := dg.I.Transaction(func(tx *gorm.DB) error {
		data, err := ReadDetailData(input, tx)
		if err != nil {
			return nil
		}
		for i := range deletePreMw {
			if err := deletePreMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}
		if err := DeleteData(data, tx); err != nil {
			return nil
		}
		for i := range deletePostMw {
			if err := deletePostMw[i](&input, &data, tx); err != nil {
				return nil
			}
		}
	})
	return &d.Data{
		Meta: d.IS{
			"source":    source,
			"structure": "single-data",
			"status":    "deleted",
		},
		Data: data,
	}, nil

}
