package main

import (
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"sync"
	"time"

	"github.com/lvhao0419/fetch_flash/bean"
	"github.com/lvhao0419/fetch_flash/model"
	"github.com/lvhao0419/fetch_flash/resource"
	"github.com/lvhao0419/fetch_flash/util"

	"github.com/bytedance/gopkg/util/gopool"
)

var (
	FlashActivityApi       = `https://flash.flashxsport.com/flash/activity/list?pageNo=%d&pageSize=%d&sortRule=0&latitude=%f&longitude=%f&area=&city=%s&nearby=&date=&levelLower=1&levelUpper=9&sportId=1&isQueryFreePlayAct=0`
	FlashActivityDetailApi = `https://flash.flashxsport.com/flash/activity/detail?activityId=%d&latitude=%f&longitude=%f`
	FlashClubDetailApi     = `https://flash.flashxsport.com/flash/club/detail?clubId=%d`
	FlashActivityGroupApi  = `https://flash.flashxsport.com/flash/activityGroup/groupListWithInitMember?activityId=%d&pageSize=10000`
)

// {"city":"北京","latitude":39.90,"longitude":116.39}
type CityParam struct {
	City      string  `json:"city"`
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

func main() {
	fmt.Println(`hello fetch_flash`)

	// 加载city_geo.json
	cityGeoJson, err := os.ReadFile("../config/city_geo.json")
	if err != nil {
		fmt.Println("Read city_geo.json failed:", err)
		return
	}

	citys := []CityParam{}
	err = json.Unmarshal(cityGeoJson, &citys)
	if err != nil {
		fmt.Println("Unmarshal city_geo.json failed:", err)
		return
	}
	fmt.Println("Loaded", len(citys), "cities from city_geo.json")

	filterCity := map[string]bool{
		"北京市":   true,
		"杭州市":   true,
		"上海市":   true,
		"成都市":   true,
		"天津市":   true,
		"武汉市":   true,
		"苏州市":   true,
		"南京市":   true,
		"济南市":   true,
		"青岛市":   true,
		"重庆市":   true,
		"深圳市":   true,
		"沈阳市":   true,
		"长春市":   true,
		"南通市":   true,
		"温州市":   true,
		"石家庄市":  true,
		"大连市":   true,
		"太原市":   true,
		"宁波市":   true,
		"珠海市":   true,
		"西安市":   true,
		"郑州市":   true,
		"贵阳市":   true,
		"东莞市":   true,
		"广州市":   true,
		"扬州市":   true,
		"呼和浩特市": true,
		"合肥市":   true,
		"长沙市":   true,
		"中山市":   true,
		"廊坊市":   true,
		"无锡市":   true,
		"海口市":   true,
	}

	for _, cityParam := range citys {
		if _, ok := filterCity[cityParam.City]; !ok {
			continue
		}

		startTime := time.Now()
		fmt.Println(`start fetch city:`, cityParam.City, `at`, startTime)
		i := 0
		for {
			i++
			fetchActivityURL := fmt.Sprintf(FlashActivityApi, i, 100, cityParam.Latitude, cityParam.Longitude, url.QueryEscape(cityParam.City))
			fmt.Println(`fetchActivityURL:`, fetchActivityURL)
			activityBody, err := util.FetchGETData(fetchActivityURL, nil)
			if err != nil {
				fmt.Println("fetch activity url data failed", err)
				break
			}

			ar := bean.ActivityRecord{}
			err = json.Unmarshal([]byte(activityBody), &ar)
			if err != nil {
				fmt.Println("unmarshal activity body failed", err)
				break
			}

			if len(ar.Result.Records) == 0 {
				break
			}

			activityData := []bean.ActivityData{}
			// fmt.Println(`ar:`, ar)
			for _, record := range ar.Result.Records {
				recordData, _ := json.Marshal(record.Data)
				if record.Type == 1 {
					ad := bean.ActivityData{}
					json.Unmarshal(recordData, &ad)
					// fmt.Println(`activityData:`, ad)
					activityData = append(activityData, ad)
				} else if record.Type == 3 {
					ads := []bean.ActivityData{}
					json.Unmarshal(recordData, &ads)
					// fmt.Println(`ads:`, ads)
					activityData = append(activityData, ads...)
				} else {
					panic(fmt.Sprintf("unexpected record type: %d", record.Type))
				}
			}

			var wg sync.WaitGroup
			p := gopool.NewPool("fetch_activity_pool", 20, gopool.NewConfig())
			for _, ad := range activityData {
				wg.Add(1)
				p.Go(func() {
					defer wg.Done()
					processActivityData(ad, cityParam)
				})
			}

			wg.Wait()
		}

		fmt.Println(`fetch_flash done, cost:`, cityParam.City, i, time.Since(startTime))
	}
}

func processActivityData(ad bean.ActivityData, cityParam CityParam) {
	dbActivity := model.Activity{}
	tx := resource.MysqlDB.Find(&dbActivity, "activityId = ?", ad.ID)
	if tx.RowsAffected > 0 {
		record := ConvertToActivityData(ad)
		record.ID = dbActivity.ID
		resource.MysqlDB.Save(&record)
		// fmt.Println(`save activity record:`, record)
	} else {
		record := ConvertToActivityData(ad)
		resource.MysqlDB.Create(&record)
		// fmt.Println(`create activity record:`, record)
	}

	fetchActivityDetailURL := fmt.Sprintf(FlashActivityDetailApi, ad.ID, cityParam.Latitude, cityParam.Longitude)
	fmt.Println(`fetchActivityDetailURL:`, fetchActivityDetailURL)
	activityDetailBody, err := util.FetchGETData(fetchActivityDetailURL, nil)
	if err != nil {
		fmt.Println("fetch activity url data failed", err)
		return
	}

	adDetail := bean.ActivityDetail{}
	err = json.Unmarshal([]byte(activityDetailBody), &adDetail)
	if err != nil {
		fmt.Println("unmarshal activity detail body failed", fetchActivityDetailURL, activityDetailBody, err)
		return
	}

	dbPlace := model.Place{}
	tx = resource.MysqlDB.Find(&dbPlace, "placeId = ?", ad.PlaceId)
	if tx.RowsAffected > 0 {
		place := ConvertToPlaceData(adDetail)
		place.ID = dbPlace.ID
		resource.MysqlDB.Save(&place)
		// fmt.Println(`save place record:`, place)
	} else {
		place := ConvertToPlaceData(adDetail)
		resource.MysqlDB.Create(&place)
		// fmt.Println(`create place record:`, place)
	}

	// 根据clubid查询俱乐部详细信息，并保存到数据库
	fetchClubDetailURL := fmt.Sprintf(FlashClubDetailApi, ad.ClubId)
	clubDetailBody, err := util.FetchGETData(fetchClubDetailURL, nil)
	if err != nil {
		fmt.Println("fetch club detail url data failed", err)
		return
	}

	cd := bean.ClubDetail{}
	err = json.Unmarshal([]byte(clubDetailBody), &cd)
	if err != nil {
		fmt.Println("unmarshal club detail body failed", err)
		return
	}

	dbClub := model.Club{}
	tx = resource.MysqlDB.Find(&dbClub, "clubId = ?", ad.ClubId)
	if tx.RowsAffected > 0 {
		club := ConvertToClubData(cd.Result)
		club.ID = dbClub.ID
		resource.MysqlDB.Save(&club)
		// fmt.Println(`save club record:`, club)
	} else {
		club := ConvertToClubData(cd.Result)
		resource.MysqlDB.Create(&club)
		// fmt.Println(`create club record:`, club)
	}

	// 获取活动报名信息
	fetchActivityGroupURL := fmt.Sprintf(FlashActivityGroupApi, ad.ID)
	fmt.Println(`fetchActivityGroupURL:`, fetchActivityGroupURL)
	activityGroupBody, err := util.FetchGETData(fetchActivityGroupURL, nil)
	if err != nil {
		fmt.Println("fetch activity group url data failed", err)
		return
	}

	activityGroupList := bean.ActivityGroupList{}
	err = json.Unmarshal([]byte(activityGroupBody), &activityGroupList)
	if err != nil {
		fmt.Println("unmarshal activity group body failed", err)
		return
	}

	// 保存报名信息
	for _, group := range activityGroupList.Result {
		// 处理普通成员
		for _, member := range group.MemberList {
			activityMember := ConvertToActivityMember(ad.ID, group.GroupName, member, 0)
			saveActivityMember(activityMember)
		}
		// 处理挂人成员
		for _, member := range group.HangMemberList {
			activityMember := ConvertToActivityMember(ad.ID, group.GroupName, member, 1)
			saveActivityMember(activityMember)
		}
	}
}

func ConvertToActivityData(ad bean.ActivityData) model.Activity {
	return model.Activity{
		ActivityID:      int32(ad.ID),
		PlaceID:         int32(ad.PlaceId),
		ClubID:          int32(ad.ClubId),
		OrganizerName:   ad.OrganizerName,
		ActivityTitle:   ad.ActivityTitle,
		ActivityAddress: ad.ActivityAddress,
		ActivityTime:    ad.ActivityTime,
		NeedMemberNum:   int32(ad.NeedMemberNum),
		ApplyMemberNum:  int32(ad.ApplyMemberNum),
		ShowCost:        ad.ShowCost,
		SportID:         int32(ad.SportId),
		AdminUID:        int32(ad.AdminUid),
		ActivityDate:    ad.ActivityDate,
		EndDate:         ad.EndDate,
		StartTime:       ad.StartTime,
		EndTime:         ad.EndTime,
		LevelLower:      int32(ad.LevelLower),
		LevelUpper:      int32(ad.LevelUpper),
		ActivityAvatar:  ad.ActivityAvatar,
		CreateTime:      time.Now(),
	}
}

func ConvertToPlaceData(ad bean.ActivityDetail) model.Place {
	return model.Place{
		PlaceID:    int32(ad.Result.PlaceDo.ID),
		PlaceName:  ad.Result.PlaceDo.PlaceName,
		City:       ad.Result.PlaceDo.City,
		Region:     ad.Result.PlaceDo.Region,
		Address:    ad.Result.PlaceDo.Address,
		Longitude:  ad.Result.PlaceDo.Longitude,
		Latitude:   ad.Result.PlaceDo.Latitude,
		CreateTime: time.Now(),
	}
}

func ConvertToClubData(cr bean.ClubDetailResult) model.Club {
	return model.Club{
		ClubID:        int32(cr.ID),
		ClubName:      cr.ClubName,
		Synopsis:      cr.Synopsis,
		MemberCount:   int32(cr.MemberCount),
		ActivityCount: int32(cr.ActivityCount),
		ContactPerson: cr.ContactPerson,
		WxCode:        cr.WxCode,
		Phone:         cr.Phone,
		Province:      cr.Province,
		City:          cr.City,
		Region:        cr.Region,
		Address:       cr.Address,
		CreateTime:    time.Now(),
	}
}

func ConvertToActivityMember(activityID int, groupName string, member bean.ActivityGroupMember, isHangPeople int) model.ActivityMember {
	isSubstitute := 0
	if member.IsSubstitute != nil {
		if sub, ok := member.IsSubstitute.(bool); ok && sub {
			isSubstitute = 1
		} else if sub, ok := member.IsSubstitute.(float64); ok && sub > 0 {
			isSubstitute = 1
		} else if sub, ok := member.IsSubstitute.(int); ok && sub > 0 {
			isSubstitute = 1
		}
	}

	// 转换interface{}类型到string
	nickname := ""
	if member.Nickname != nil {
		if n, ok := member.Nickname.(string); ok {
			nickname = n
		} else if n, ok := member.Nickname.(float64); ok {
			nickname = fmt.Sprintf("%v", n)
		} else if n, ok := member.Nickname.(int); ok {
			nickname = fmt.Sprintf("%v", n)
		}
	}

	realname := ""
	if member.Realname != nil {
		if r, ok := member.Realname.(string); ok {
			realname = r
		} else if r, ok := member.Realname.(float64); ok {
			realname = fmt.Sprintf("%v", r)
		} else if r, ok := member.Realname.(int); ok {
			realname = fmt.Sprintf("%v", r)
		}
	}

	sex := ""
	if member.Sex != nil {
		if s, ok := member.Sex.(string); ok {
			sex = s
		} else if s, ok := member.Sex.(float64); ok {
			sex = fmt.Sprintf("%v", s)
		} else if s, ok := member.Sex.(int); ok {
			sex = fmt.Sprintf("%v", s)
		}
	}

	phone := ""
	if member.Phone != nil {
		if p, ok := member.Phone.(string); ok {
			phone = p
		} else if p, ok := member.Phone.(float64); ok {
			phone = fmt.Sprintf("%v", p)
		} else if p, ok := member.Phone.(int); ok {
			phone = fmt.Sprintf("%v", p)
		}
	}

	level := ""
	if member.Level != nil {
		if l, ok := member.Level.(string); ok {
			level = l
		} else if l, ok := member.Level.(float64); ok {
			level = fmt.Sprintf("%v", l)
		} else if l, ok := member.Level.(int); ok {
			level = fmt.Sprintf("%v", l)
		}
	}

	payStatus := ""
	if member.PayStatus != nil {
		if ps, ok := member.PayStatus.(string); ok {
			payStatus = ps
		} else if ps, ok := member.PayStatus.(float64); ok {
			payStatus = fmt.Sprintf("%v", ps)
		} else if ps, ok := member.PayStatus.(int); ok {
			payStatus = fmt.Sprintf("%v", ps)
		}
	}

	payType := ""
	if member.PayType != nil {
		if pt, ok := member.PayType.(string); ok {
			payType = pt
		} else if pt, ok := member.PayType.(float64); ok {
			payType = fmt.Sprintf("%v", pt)
		} else if pt, ok := member.PayType.(int); ok {
			payType = fmt.Sprintf("%v", pt)
		}
	}

	payAmount := ""
	if member.PayAmount != nil {
		if pa, ok := member.PayAmount.(string); ok {
			payAmount = pa
		} else if pa, ok := member.PayAmount.(float64); ok {
			payAmount = fmt.Sprintf("%v", pa)
		} else if pa, ok := member.PayAmount.(int); ok {
			payAmount = fmt.Sprintf("%v", pa)
		}
	}

	return model.ActivityMember{
		ActivityID:   int32(activityID),
		UserID:       member.UserID,
		MemberID:     member.MemberID,
		Avatar:       member.Avatar,
		Nickname:     nickname,
		Realname:     realname,
		Sex:          sex,
		Phone:        phone,
		Level:        level,
		GroupName:    groupName,
		IsSubstitute: int64(isSubstitute),
		IsHangPeople: int64(isHangPeople),
		PayStatus:    payStatus,
		PayType:      payType,
		PayAmount:    payAmount,
		CreateTime:   time.Now(),
		UpdateTime:   time.Now(),
	}
}

func saveActivityMember(activityMember model.ActivityMember) {
	var dbActivityMember model.ActivityMember
	tx := resource.MysqlDB.Find(&dbActivityMember, "activityId = ? AND userId = ?", activityMember.ActivityID, activityMember.UserID)
	if tx.RowsAffected > 0 {
		// 更新现有记录
		activityMember.ID = dbActivityMember.ID
		resource.MysqlDB.Save(&activityMember)
		// fmt.Println("update activity member record:", activityMember)
	} else {
		// 创建新记录
		resource.MysqlDB.Create(&activityMember)
		// fmt.Println("create activity member record:", activityMember)
	}
}

func init() {
	resource.InitDB()
}
