package main

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"github.com/spf13/viper"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"log"
	"sync"
	"time"

	"github.com/UpmeshLTD/urus-aio/infrastructure/common"
	"github.com/avast/retry-go"
)

type SubLevelLocation struct {
	InternationalName string
	LocalizedName     string
}

type ReceiverInfoOld struct {
	Name              string
	RecipientName     string
	Email             string
	ContactNumber     string
	CountryCode       string
	PostalCode        string
	AddressLine1      string
	AddressLine2      string
	SubLevelLocations []string
}

type ReceiverInfo struct {
	Name              string
	RecipientName     string
	Email             string
	ContactNumber     string
	CountryCode       string
	PostalCode        string
	AddressLine1      string
	AddressLine2      string
	SubLevelLocations []SubLevelLocation
}

func (v ReceiverInfoOld) Value() (driver.Value, error) {
	return json.Marshal(&v)
}

func (v *ReceiverInfoOld) Scan(value interface{}) error {
	return jsonScan(value, v, func() {
		*v = ReceiverInfoOld{}
	})
}

func (v ReceiverInfo) Value() (driver.Value, error) {
	return json.Marshal(&v)
}

func (v *ReceiverInfo) Scan(value interface{}) error {
	return jsonScan(value, v, func() {
		*v = ReceiverInfo{}
	})
}

/*--------------------------------------------------------------------------------------------------------------------*/

// jsonScan scans a src value to dest object. If the src data is nil, dest is set to zero value by given setZero function.
func jsonScan(src interface{}, dest interface{}, setZero func()) error {
	if src == nil {
		setZero()
		return nil
	}

	var bytes []byte
	switch v := src.(type) {
	case []byte:
		bytes = v
	case string:
		bytes = []byte(v)
	}

	return json.Unmarshal(bytes, dest)
}

/*--------------------------------------------------------------------------------------------------------------------*/

type Shipment struct {
	ID              string
	ReceiverInfo    *ReceiverInfo
	ReceiverInfoOld *ReceiverInfoOld
}

type Config struct {
	Host     string `mapstructure:"DB_HOST"`
	Username string `mapstructure:"DB_USERNAME"`
	Password string `mapstructure:"DB_PASSWORD"`
	Database string `mapstructure:"DB_DATABASE"`
	Port     string `mapstructure:"DB_PORT"`
}

func main() {
	var (
		config Config
		db     *gorm.DB
		err    error
	)

	viper.AutomaticEnv()
	viper.SetConfigFile(".env")
	viper.AddConfigPath(".")

	_ = viper.MergeInConfig()
	_ = viper.ReadInConfig()
	_ = viper.Unmarshal(&config)

	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
		config.Host, config.Username, config.Password, config.Database, config.Port)

	err = retry.Do(func() error {
		db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
		return err
	},
		retry.Attempts(3),
		retry.Delay(time.Second),
	)

	if err != nil {
		log.Fatal(err)
	}

	var (
		results    []*Shipment
		pagination = &common.Pagination{
			Limit: 100,
			Page:  1,
		}
	)
	builder := db.Model(&Shipment{}).Select("id, receiver_info, receiver_info_old")
	for {
		builder = builder.Scopes(common.Paginate(results, pagination, builder))
		if err := builder.Where("receiver_info is null AND receiver_info_old is not null").Find(&results).Error; err != nil {
			log.Fatal(err)
		}

		log.Printf("Total: %d\n", pagination.Total)

		if len(results) == 0 {
			break
		}

		wg := sync.WaitGroup{}
		wg.Add(len(results))
		for _, v := range results {
			v := v
			log.Println("Shipment ID: " + v.ID)
			go func() {
				wg.Done()
				v.ReceiverInfo = convertReceiverInfo(v.ReceiverInfoOld)
				if err := db.Model(&v).Updates(&v).Error; err != nil {
					log.Println(err)
				}
			}()
		}
		wg.Wait()
		log.Printf("Done %d\n", len(results))
		time.Sleep(time.Second)
	}
}

func convertReceiverInfo(info *ReceiverInfoOld) *ReceiverInfo {
	subLevels := make([]SubLevelLocation, 0)
	for _, v := range info.SubLevelLocations {
		subLevels = append(subLevels, SubLevelLocation{
			InternationalName: v,
			LocalizedName:     v,
		})
	}
	return &ReceiverInfo{
		Name:              info.Name,
		RecipientName:     info.RecipientName,
		Email:             info.Email,
		ContactNumber:     info.ContactNumber,
		CountryCode:       info.CountryCode,
		PostalCode:        info.PostalCode,
		AddressLine1:      info.AddressLine1,
		AddressLine2:      info.AddressLine2,
		SubLevelLocations: subLevels,
	}
}
