package webapi

import (
	"fmt"
	"time"

	"github.com/jackc/pgtype"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func (toc *TrainOperator) IsEmpty() bool {
	return toc == nil || toc.Code == ""
}

func (toc *TrainOperator) Canonicalize() {
	if toc == nil {
		return
	}
}

func (loc *Location) IsEmpty() bool {
	return loc == nil || loc.Tiploc == ""
}

func (loc *Location) Canonicalize() {
	if loc == nil {
		return
	}
	if loc.Operator.IsEmpty() {
		loc.Operator = nil
	}
	loc.Operator.Canonicalize()
}

func (dr *DisruptionReason) IsEmpty() bool {
	return dr == nil || dr.Code == 0
}

func (dr *DisruptionReason) Canonicalize() {
	if dr == nil {
		return
	}
	if dr.Location.IsEmpty() {
		dr.Location = nil
	}
	dr.Location.Canonicalize()
}

func (sd *ServiceData) Canonicalize() {
	if sd.DelayReason.IsEmpty() {
		sd.DelayReason = nil
	}
	sd.DelayReason.Canonicalize()
	if sd.CancelReason.IsEmpty() {
		sd.CancelReason = nil
	}
	sd.CancelReason.Canonicalize()
}

func (tz *TimeZone) ToLocation() (*time.Location, error) {
	return time.LoadLocation(tz.GetName())
}

func (dt *DateTime) ToTime() (time.Time, error) {
	tz := dt.GetTimezone()
	if tz == nil {
		return time.Time{}, fmt.Errorf("no timezone specified")
	}
	loc, err := tz.ToLocation()
	if err != nil {
		return time.Time{}, fmt.Errorf("loading timezone (%s / offset %v): %w", tz.GetName(), tz.GetUtcOffset(), err)
	}
	return dt.GetTimestamp().AsTime().In(loc), nil
}

func (dt *DateTime) decodeWith(ci *pgtype.ConnInfo, src []byte, dec func(*pgtype.Timestamptz, *pgtype.ConnInfo, []byte) error) error {
	dt.Timestamp = nil
	dt.Timezone = nil

	if src == nil {
		return nil
	}

	ttz := &pgtype.Timestamptz{}
	if err := dec(ttz, ci, src); err != nil {
		return err
	}

	if ttz.Status != pgtype.Present {
		return nil
	}

	*dt = *(FromTime(ttz.Time))

	return nil
}

func (dt *DateTime) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error {
	return dt.decodeWith(ci, src, (*pgtype.Timestamptz).DecodeBinary)
}

func (dt *DateTime) DecodeText(ci *pgtype.ConnInfo, src []byte) error {
	return dt.decodeWith(ci, src, (*pgtype.Timestamptz).DecodeText)
}

func (dt *DateTime) AssignTo(dst interface{}) error {
	if dt == nil {
		return pgtype.NullAssignTo(dst)
	}

	switch dst := dst.(type) {
	case (*DateTime):
		*dst = *dt
		return nil
	default:
		if nextDst, retry := pgtype.GetAssignToDstType(dst); retry {
			return dt.AssignTo(nextDst)
		}
		return fmt.Errorf("unable to assign to %T, sad trombone", dst)
	}

	return fmt.Errorf("cannot decode %#v into %T, sad trombone", dt, dst)
}

func FromTime(t time.Time) *DateTime {
	zName, zOffset := t.Zone()
	tz := &TimeZone{
		Name:      zName,
		UtcOffset: int32(zOffset),
	}

	return &DateTime{
		Timestamp: timestamppb.New(t),
		Timezone:  tz,
	}
}

func (dt *DateTime) IsZero() bool {
	if dt.GetTimestamp() == nil {
		return true
	}
	t, err := dt.ToTime()
	if err != nil {
		return true
	}
	return t.IsZero()
}

func (td *TimingData) IsEmpty() bool {
	if td == nil {
		return true
	}
	return td.PublicScheduled.IsZero() && td.WorkingScheduled.IsZero() && td.PublicEstimated.IsZero() && td.WorkingEstimated.IsZero() && td.Actual.IsZero()
}

func (td *TimingData) Canonicalize() {
	if td == nil {
		return
	}
	ts := []**DateTime{&td.PublicScheduled, &td.WorkingScheduled, &td.PublicEstimated, &td.WorkingEstimated, &td.Actual}
	for _, t := range ts {
		if *t == nil || (*t).IsZero() {
			*t = nil
		}
	}
}

func (pd *PlatformData) IsEmpty() bool {
	return pd.Scheduled == "" && pd.Live == ""
}

func (pd *PlatformData) Canonicalize() {}

func (sl *ServiceLocation) Canonicalize() {
	if sl == nil {
		return
	}
	sl.Location.Canonicalize()
	if sl.Platform.IsEmpty() {
		sl.Platform = nil
	}
	sl.Platform.Canonicalize()
	if sl.FalseDestination.IsEmpty() {
		sl.FalseDestination = nil
	}
	sl.FalseDestination.Canonicalize()
	if sl.ArrivalTiming.IsEmpty() {
		sl.ArrivalTiming = nil
	}
	sl.ArrivalTiming.Canonicalize()
	if sl.DepartureTiming.IsEmpty() {
		sl.DepartureTiming = nil
	}
	sl.DepartureTiming.Canonicalize()
	if sl.PassTiming.IsEmpty() {
		sl.PassTiming = nil
	}
	sl.PassTiming.Canonicalize()
}