package mapping

import (
	"encoding/json"
	"log"
	"time"

	"gorm.io/datatypes"

	api "github.com/moustachioed/dash/backend/dashapi"
	db "github.com/moustachioed/dash/backend/database/models"
)

type MapperImpl struct{}

func NewMapperImpl() Mapper {
	return MapperImpl{}
}

func (mapper MapperImpl) JournalApiToDs(am interface{}) interface{} {
	apimodel := am.(api.JournalEntry)
	date, err := mapper.StringToDate(apimodel.Date)
	if err != nil {
		log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
	}

	thankful, err := json.Marshal(apimodel.Thankful)
	if err != nil {
		thankful = nil
	}

	lookingForward, err := json.Marshal(apimodel.LookingForward)
	if err != nil {
		lookingForward = nil
	}

	beenGreat, err := json.Marshal(apimodel.BeenGreat)
	if err != nil {
		beenGreat = nil
	}

	doBetter, err := json.Marshal(apimodel.DoBetter)
	if err != nil {
		doBetter = nil
	}

	return db.Journal{
		Date:           datatypes.Date(date),
		Thankful:       datatypes.JSON(thankful),
		LookingForward: datatypes.JSON(lookingForward),
		BeenGreat:      datatypes.JSON(beenGreat),
		DoBetter:       datatypes.JSON(doBetter),
		Journal:        apimodel.Journal,
	}
}

func (mapper MapperImpl) JournalDsToApi(dm interface{}) interface{} {
	dbmodel := dm.(db.Journal)

	dateValue, err := dbmodel.Date.Value()
	var date string
	if err != nil {
		date = ""
	} else {
		// date = dateValue.(time.Time).Format("2006-01-02")
		date = mapper.DateToString(dateValue.(time.Time))
	}

	var thankful []string
	err = json.Unmarshal(dbmodel.Thankful, &thankful)
	if err != nil {
		thankful = nil
	}

	var lookingForward []string
	err = json.Unmarshal(dbmodel.LookingForward, &lookingForward)
	if err != nil {
		lookingForward = nil
	}

	var beenGreat []string
	err = json.Unmarshal(dbmodel.BeenGreat, &beenGreat)
	if err != nil {
		beenGreat = nil
	}

	var doBetter []string
	err = json.Unmarshal(dbmodel.DoBetter, &doBetter)
	if err != nil {
		doBetter = nil
	}

	return api.JournalEntry{
		Date:           date,
		Thankful:       thankful,
		LookingForward: lookingForward,
		BeenGreat:      beenGreat,
		DoBetter:       doBetter,
		Journal:        dbmodel.Journal,
	}
}

func (mapper MapperImpl) PlanDayApiToDs(am interface{}) interface{} {
	apimodel := am.(api.PlanDay)
	date, err := mapper.StringToDate(apimodel.Date)
	if err != nil {
		log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
	}

	morning, err := json.Marshal(apimodel.Morning)
	if err != nil {
		morning = nil
	}

	midday, err := json.Marshal(apimodel.Midday)
	if err != nil {
		midday = nil
	}

	afternoon, err := json.Marshal(apimodel.Afternoon)
	if err != nil {
		afternoon = nil
	}

	evening, err := json.Marshal(apimodel.Evening)
	if err != nil {
		evening = nil
	}

	pleasant, err := json.Marshal(apimodel.Pleasant)
	if err != nil {
		pleasant = nil
	}

	reminders, err := json.Marshal(apimodel.Reminders)
	if err != nil {
		reminders = nil
	}

	return db.PlanDay{
		Date:      datatypes.Date(date),
		Morning:   datatypes.JSON(morning),
		Midday:    datatypes.JSON(midday),
		Afternoon: datatypes.JSON(afternoon),
		Evening:   datatypes.JSON(evening),
		Pleasant:  datatypes.JSON(pleasant),
		Reminders: datatypes.JSON(reminders),
	}
}

func (mapper MapperImpl) PlanDayDsToApi(dm interface{}) interface{} {
	dbmodel := dm.(db.PlanDay)

	dateValue, err := dbmodel.Date.Value()
	var date string
	if err != nil {
		date = ""
	} else {
		date = mapper.DateToString(dateValue.(time.Time))
	}

	var morning []string
	err = json.Unmarshal(dbmodel.Morning, &morning)
	if err != nil {
		morning = nil
	}

	var midday []string
	err = json.Unmarshal(dbmodel.Midday, &midday)
	if err != nil {
		midday = nil
	}

	var afternoon []string
	err = json.Unmarshal(dbmodel.Afternoon, &afternoon)
	if err != nil {
		afternoon = nil
	}

	var evening []string
	err = json.Unmarshal(dbmodel.Evening, &evening)
	if err != nil {
		evening = nil
	}

	var pleasant []string
	err = json.Unmarshal(dbmodel.Pleasant, &pleasant)
	if err != nil {
		pleasant = nil
	}

	var reminders []string
	err = json.Unmarshal(dbmodel.Reminders, &reminders)
	if err != nil {
		reminders = nil
	}

	return api.PlanDay{
		Date:      date,
		Morning:   morning,
		Midday:    midday,
		Afternoon: afternoon,
		Evening:   evening,
		Pleasant:  pleasant,
		Reminders: reminders,
	}
}

func (mapper MapperImpl) PlanWeekApiToDs(am interface{}) (dm interface{}) {
	apimodel := am.(api.PlanWeek)
	date, err := mapper.StringToDate(apimodel.Date)
	if err != nil {
		log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
	}

	items, err := json.Marshal(apimodel.Items)
	if err != nil {
		items = nil
	}

	return db.PlanWeek{
		Date:  datatypes.Date(date),
		Items: items,
	}
}

func (mapper MapperImpl) PlanWeekDsToApi(dm interface{}) (am interface{}) {
	dbmodel := dm.(db.PlanWeek)

	dateValue, err := dbmodel.Date.Value()
	var date string
	if err != nil {
		date = ""
	} else {
		date = mapper.DateToString(dateValue.(time.Time))
	}

	var items []api.PlanWeekItem
	err = json.Unmarshal(dbmodel.Items, &items)
	if err != nil {
		items = nil
	}

	return api.PlanWeek{
		Date:  date,
		Items: items,
	}
}

// write a function that takes a month and returns a plan month
func (mapper MapperImpl) PlanMonthApiToDs(am interface{}) (dm interface{}) {
	apimodel := am.(api.PlanMonth)
	date, err := mapper.StringToDate(apimodel.Date)
	if err != nil {
		log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
	}

	items, err := json.Marshal(apimodel.Items)
	if err != nil {
		items = nil
	}

	return db.PlanMonth{
		Date:  datatypes.Date(date),
		Items: items,
	}
}

// write a function that takes a plan month and returns a month
func (mapper MapperImpl) PlanMonthDsToApi(dm interface{}) (am interface{}) {
	dbmodel := dm.(db.PlanMonth)

	dateValue, err := dbmodel.Date.Value()
	var date string
	if err != nil {
		date = ""
	} else {
		date = mapper.DateToString(dateValue.(time.Time))
	}

	var items []string
	err = json.Unmarshal(dbmodel.Items, &items)
	if err != nil {
		items = nil
	}

	return api.PlanMonth{
		Date:  date,
		Items: items,
	}
}

func (mapper MapperImpl) TrackingCategoriesDbToApi(dm interface{}) (am interface{}) {
	dbmodel := dm.([]db.TrackingCategory)

	var categories []api.TrackingCategory
	for _, category := range dbmodel {
		var items []string
		err := json.Unmarshal(category.Items, &items)
		if err != nil {
			items = nil
		}

		categories = append(categories, api.TrackingCategory{
			Name:  category.Name,
			Type:  category.Type,
			Items: items,
		})
	}

	return api.TrackingCategories{
		Categories: categories,
	}
}

func (mapper MapperImpl) TrackingEntryDbToApi(dm interface{}) (am interface{}) {
	dbmodel := dm.([]db.TrackingItem)

	var items []api.TrackingItem

	for _, item := range dbmodel {
		items = append(items, api.TrackingItem{
			Date:  mapper.DateToString(item.Date),
			Type:  item.Type,
			Value: item.Value,
		})
	}

	if len(items) == 0 {
		return api.TrackingEntry{}
	} else {
		return api.TrackingEntry{
			Date:  mapper.DateToString(dbmodel[0].Date),
			Items: items,
		}
	}
}

func (mapper MapperImpl) TrackingEntryApiToDb(am interface{}) (dm interface{}) {
	apimodel := am.(api.TrackingEntry)

	date, err := mapper.StringToDate(apimodel.Date)
	if err != nil {
		log.Printf("[ERROR] Could not parse date `%s`", apimodel.Date)
	}

	var items []db.TrackingItem
	for _, item := range apimodel.Items {
		items = append(items, db.TrackingItem{
			Date:  date,
			Type:  item.Type,
			Value: item.Value,
		})
	}

	return items
}

func (mapper MapperImpl) InboxApiToDs(am interface{}) (dm interface{}) {
	apimodel := am.(api.InboxItem)

	return db.Inbox{
		Id:   apimodel.Id,
		Item: apimodel.Item,
	}
}

func (mapper MapperImpl) InboxDsToApi(dm interface{}) (am interface{}) {
	dbmodel := dm.(db.Inbox)

	return api.InboxItem{
		Id:   dbmodel.Id,
		Item: dbmodel.Item,
	}
}

func (mapper MapperImpl) StringToDate(dateString string) (time.Time, error) {
	date, err := time.Parse("2006-01-02", dateString)
	if err != nil {
		log.Printf("[ERROR] Could not parse date string %s", dateString)
		return time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), err
	}
	return date, nil
}

func (mapper MapperImpl) DateToString(date time.Time) string {
	return date.Format("2006-01-02")
}
