package health

import (
	"fmt"
	"sync"
	"time"

	"github.com/eternal-flame-AD/yoake/internal/db"
	"github.com/eternal-flame-AD/yoake/internal/echoerror"
	"github.com/eternal-flame-AD/yoake/internal/util"
	"github.com/labstack/echo/v4"
)

const yearAbsZero = 2000

func RESTComplianceLogGet(database db.DB) func(c echo.Context) error {
	return func(c echo.Context) error {
		filterKeyname := c.Param("med")
		from := c.QueryParam("from")
		to := c.QueryParam("to")
		if to == "" {
			to = time.Now().Format("2006-01-02")
		}
		if from == "" {
			from = time.Now().AddDate(0, 0, -30).Format("2006-01-02")
		}
		fromTime, err := time.Parse("2006-01-02", from)
		if err != nil {
			return echoerror.NewHttp(400, err)
		}
		toTime, err := time.Parse("2006-01-02", to)
		if err != nil {
			return echoerror.NewHttp(400, err)
		}
		period := util.NewDateRange(fromTime, toTime)
		if days := period.Days(); days > 180 {
			return echoerror.NewHttp(400, fmt.Errorf("invalid date range: %v", period))
		}
		logs, err := DBMedComplianceLogGet(database, period)
		if db.IsNotFound(err) || logs == nil {
			return c.JSON(200, ComplianceLogList{})
		} else if err != nil {
			return echoerror.NewHttp(500, err)
		}
		if filterKeyname != "" {
			filtered := make(ComplianceLogList, 0, len(logs))
			for _, log := range logs {
				if log.MedKeyname == filterKeyname {
					filtered = append(filtered, log)
				}
			}
			logs = filtered
		}

		return c.JSON(200, logs)
	}
}

func RESTComplianceLogPost(db db.DB, writeMutex *sync.Mutex) echo.HandlerFunc {
	return func(c echo.Context) error {
		var input ComplianceLog
		if err := c.Bind(&input); err != nil {
			return echoerror.NewHttp(400, err)
		}
		if input.Actual.Time.IsZero() {
			return echoerror.NewHttp(400, fmt.Errorf("invalid date"))
		}
		writeMutex.Lock()
		defer writeMutex.Unlock()

		meds, err := DBMedListGet(db)
		if err != nil {
			return echoerror.NewHttp(500, err)
		}

		var dir *Direction
		for _, med := range meds {
			d := med
			if med.KeyName() == input.MedKeyname {
				dir = &d
			} else if med.Name == input.MedKeyname {
				input.MedKeyname = med.KeyName()
				dir = &d
			}
		}
		if dir == nil {
			return echoerror.NewHttp(404, fmt.Errorf("med not found"))
		}

		if err := DBMedComplianceLogSetOne(db, *dir, &input); err != nil {
			return err
		}

		if input.Actual.Dose <= 0 {
			return c.NoContent(204)
		}

		return c.JSON(200, input)
	}
}

func RESTComplianceLogProjectMed(db db.DB) func(c echo.Context) error {
	return func(c echo.Context) error {
		keyName := c.Param("med")
		meds, err := DBMedListGet(db)
		if err != nil {
			return echoerror.NewHttp(500, err)
		}

		var dir *Direction
		for _, med := range meds {
			if med.KeyName() == keyName {
				d := med
				dir = &d
			}
		}
		if dir == nil {
			return echoerror.NewHttp(404, fmt.Errorf("med not found"))
		}

		complianceLog, err := DBMedComplianceLogGet(db, util.DateRangeAround(time.Now(), 1))
		if err != nil {
			return echoerror.NewHttp(500, err)
		}

		return c.JSON(200, complianceLog.ProjectNextDose(*dir))
	}
}

func RESTRecalcMedComplianceLog(db db.DB, writeMutex *sync.Mutex) func(c echo.Context) error {
	return func(c echo.Context) error {
		meds, err := DBMedListGet(db)
		if err != nil {
			return echoerror.NewHttp(500, err)
		}

		from := time.Date(yearAbsZero, 1, 1, 0, 0, 0, 0, time.UTC)
		to := time.Now()
		if fromStr := c.QueryParam("from"); fromStr != "" {
			from, err = time.Parse("2006-01", fromStr)
			if err != nil {
				return echoerror.NewHttp(400, err)
			}
		}
		if toStr := c.QueryParam("to"); toStr != "" {
			to, err = time.Parse("2006-01", toStr)
			if err != nil {
				return echoerror.NewHttp(400, err)
			}
		}

		writeMutex.Lock()
		defer writeMutex.Unlock()
		for year := from.Year(); year <= to.Year(); year++ {
			for month := 1; month <= 12; month++ {
				if year == from.Year() && month < int(from.Month()) {
					continue
				}
				if year == to.Year() && month > int(to.Month()) {
					continue
				}

				log, err := DBMedComplianceLogGet(db, util.DateRangeAround(time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC), 1))
				if err != nil {
					return echoerror.NewHttp(500, err)
				}
				if len(log) == 0 {
					continue
				}

				for _, dir := range meds {
					log.UpdateDoseOffset(dir)
				}
				if err := DBMedComplianceLogAppend(db, log); err != nil {
					return echoerror.NewHttp(500, err)
				}
			}
		}

		return c.NoContent(204)
	}
}