package vstp

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"git.fjla.uk/owlboard/go-types/pkg/database"
	"git.fjla.uk/owlboard/go-types/pkg/upstreamApi"
	"git.fjla.uk/owlboard/timetable-mgr/helpers"
	"git.fjla.uk/owlboard/timetable-mgr/log"
)

// Unmarshals the JSON data and runs it through the formatData() function and returns the data in a DB ready Struct
func unmarshalData(jsonData string) database.Service {
	var schedule upstreamApi.MsgData
	err := json.Unmarshal([]byte(jsonData), &schedule)
	if err != nil {
		log.Msg.Error("Unable to unmarshal message body: " + err.Error())
		//return err
	}
	log.Msg.Debug("Unmarshalling Complete")

	if schedule.Data.CIFMsg.ScheduleSegment == nil {
		log.Msg.Warn("ScheduleSegment is nil")
	} else if len(schedule.Data.CIFMsg.ScheduleSegment) == 0 {
		log.Msg.Warn("ScheduleSegment is empty")
	}
	return formatData(&schedule.Data.CIFMsg)
}

// Transforms the upstreamApi.Schedule type into a database.Service type
func formatData(dataInput *upstreamApi.Schedule) database.Service {
	log.Msg.Debug("ScheduleSegment length: " + fmt.Sprint(len(dataInput.ScheduleSegment)))
	log.Msg.Debug("Printing dataInput to console:")

	var operator, headcode, powerType string
	var planSpeed int32
	var stops []database.Stop

	// Check that the ScheduleSegment contains data, 'Delete' messages have no ScheduleSegment
	if len(dataInput.ScheduleSegment) > 0 {
		operator = dataInput.ScheduleSegment[0].ATOCCode
		headcode = dataInput.ScheduleSegment[0].SignallingID
		powerType = dataInput.ScheduleSegment[0].CIFPowerType
		planSpeed = parseSpeed(dataInput.ScheduleSegment[0].CIFSpeed)
		stops = parseStops(dataInput.ScheduleSegment[0].ScheduleLocation)
	}
	if operator == "" {
		operator = "UK"
	}
	service := database.Service{
		TransactionType:   dataInput.TransactionType,
		StpIndicator:      dataInput.CIFSTPIndicator,
		Vstp:              true,
		Operator:          operator,
		TrainUid:          dataInput.CIFTrainUID,
		Headcode:          headcode,
		PowerType:         powerType,
		PlanSpeed:         planSpeed,
		ScheduleStartDate: parseDate(dataInput.ScheduleStartDate, false),
		ScheduleEndDate:   parseDate(dataInput.ScheduleEndDate, true),
		DaysRun:           parseDaysRun(dataInput.ScheduleDaysRun),
		Stops:             stops,
	}
	return service
}

// Uses the map provided in 'helpers' to translate incorrect CIF speeds to their correct equivalent
func parseSpeed(CIFSpeed string) int32 {
	log.Msg.Debug("CIFSpeed Input: '" + CIFSpeed + "'")
	if CIFSpeed == "" {
		log.Msg.Debug("Speed data not provided")
		return int32(0)
	}
	actualSpeed, exists := helpers.SpeedMap[CIFSpeed]
	if !exists {
		actualSpeed = CIFSpeed
	}
	log.Msg.Debug("Corrected Speed: " + actualSpeed)

	speed, err := strconv.ParseInt(actualSpeed, 10, 32)
	if err != nil {
		log.Msg.Warn("Unable to parse speed: " + CIFSpeed + ", returning 0")
		return int32(0)
	}
	return int32(speed)
}

// Converts the date string provided from the upstream API into a proper Date type and adds a time
func parseDate(dateString string, end bool) time.Time {
	log.Msg.Debug("Date Input: " + dateString)
	date, err := time.Parse("2006-01-02", dateString)
	if err != nil {
		log.Msg.Error("Unable to parse date: " + dateString)
		return time.Time{}
	}

	var hour, minute, second, nanosecond int
	location := time.UTC
	if end {
		hour, minute, second, nanosecond = 23, 59, 59, 999999999
	} else {
		hour, minute, second, nanosecond = 0, 0, 0, 0
	}

	dateWithTime := time.Date(date.Year(), date.Month(), date.Day(), hour, minute, second, nanosecond, location)
	log.Msg.Debug("Parsed date: " + dateWithTime.String())
	return dateWithTime
}

// Converts the binary stype 'daysRun' field into an array of short days
func parseDaysRun(daysBinary string) []string {
	log.Msg.Debug("daysRun Input: " + daysBinary)
	shortDays := []string{"m", "t", "w", "th", "f", "s", "su"}
	var result []string
	for i, digit := range daysBinary {
		if digit == '1' {
			result = append(result, shortDays[i])
		}
	}
	return result
}

// Converts an array if upstreamApi.ScheduleLocation types to an array of database.Stop types
func parseStops(inputStops []upstreamApi.ScheduleLocation) []database.Stop {
	var stops []database.Stop

	for _, loc := range inputStops {
		stop := database.Stop{
			PublicDeparture: parseTimeStrings(loc.PublicDepartureTime),
			WttDeparture:    parseTimeStrings(loc.ScheduledDepartureTime),
			PublicArrival:   parseTimeStrings(loc.PublicArrivalTime),
			WttArrival:      parseTimeStrings(loc.ScheduledArrivalTime),
			IsPublic:        strings.TrimSpace(loc.PublicDepartureTime) != "" || strings.TrimSpace(loc.PublicArrivalTime) != "",
			Tiploc:          loc.Tiploc.Tiploc.TiplocId,
		}

		stops = append(stops, stop)
	}

	return stops
}

func parseTimeStrings(t string) string {
	if t == "" {
		return t
	}

	strippedT := strings.TrimSpace(t)
	if strippedT == "" {
		return ""
	} else {
		return strippedT[:4]
	}
}
