package utils

import (
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"golang.org/x/text/encoding/simplifiedchinese"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	// DefaultDict 默认字典
	DefaultDict = "./conf/data.dat"
	// IndexLen 索引长度
	IndexLen = 7
	// RedirectMode1 国家的类型, 指向另一个指向
	RedirectMode1 = 0x01
	// RedirectMode2 国家的类型, 指向一个指向
	RedirectMode2 = 0x02
)

type Result struct {
	IP       string   `json:"ip"`
	Location Location `json:"location"`
	AdInfo   AdInfo   `json:"ad_info"`
}

type ResultV2 struct {
	IP      string `json:"ip"`
	City    string `json:"city"`
	Country string `json:"country"`
}

type Location struct {
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
}

type AdInfo struct {
	Nation   string `json:"nation"`
	Province string `json:"province"`
	City     string `json:"city"`
	District string `json:"district"`
	AdcodE   int    `json:"adcode"`
}

type IpResult struct {
	Status    int    `json:"status"`
	Message   string `json:"message"`
	RequestID string `json:"request_id"`
	Result    Result `json:"result"`
}

type IpResultV2 struct {
	Ret    int      `json:"ret"`
	Msg    string   `json:"Msg"`
	LogId  string   `json:"log_id"`
	Result ResultV2 `json:"data"`
}

type IPDict struct {
	fileData    []byte //文件数据
	offset      uint32 //当前下标定位
	firstOffset uint32 //第一条IP记录的偏移地址
	lastOffset  uint32 //最后一条IP记录的偏移地址
	totalIPNum  uint32 //IP记录的总条数（不包含版本信息记录）
}

type IPLocation struct {
	IP      string `json:"ip"`
	BeginIP string `json:"begin_ip"`
	EndIP   string `json:"end_ip"`
	Country string `json:"country"`
	Area    string `json:"area"`
}

func NewIPDict() *IPDict {
	return &IPDict{}
}

func (q *IPDict) Load(fileName string) error {
	filePath, err := dictPath(fileName)
	if err != nil {
		return err
	}
	dictFile, err := os.OpenFile(filePath, os.O_RDONLY, 0400)
	if err != nil {
		return err
	}
	defer dictFile.Close()
	q.fileData, err = ioutil.ReadAll(dictFile)
	if err != nil {
		return err
	}
	buf := q.readBuf(8)
	q.firstOffset = binary.LittleEndian.Uint32(buf[:4])
	q.lastOffset = binary.LittleEndian.Uint32(buf[4:])
	q.totalIPNum = (q.lastOffset - q.firstOffset) / IndexLen
	return nil
}

func (q *IPDict) FindIP(ip string) (*IPLocation, error) {
	if false == checkIPv4(ip) {
		return nil, errors.New("IP format error")
	}
	res := IPLocation{IP: ip}
	if nil == q.fileData {
		err := q.Load(DefaultDict)
		if nil != err {
			return nil, err
		}
	}
	q.seekOffset(0)
	index := q.findIndex(ip)
	if index <= 0 {
		return nil, errors.New("IP not fount")
	}
	q.seekOffset(index)
	res.BeginIP = long2ip(q.getIPLong4()) //endIPOffset
	endIPOffset := q.getRedirectOffset()
	q.seekOffset(endIPOffset)
	res.EndIP = long2ip(q.getIPLong4()) //endIPOffset
	mode := q.readMode()                // 标志字节
	var country, area []byte
	enc := simplifiedchinese.GBK.NewDecoder()
	switch mode {
	case RedirectMode1: // 标志字节为1，表示国家和区域信息都被同时重定向
		countryOffset := q.getRedirectOffset() // 重定向地址
		q.seekOffset(countryOffset)
		mode2 := q.readMode() // 标志字节
		switch mode2 {
		case RedirectMode2: // 标志字节为2，表示国家信息又被重定向
			q.seekOffset(q.getRedirectOffset()) // 重定向地址
			country = q.readString(0)
			q.seekOffset(countryOffset + 4) // 重定向地址
			area = q.readArea()
		default: // 否则，表示国家信息没有被重定向
			country = q.readString(mode2)
			area = q.readArea()
		}
	case RedirectMode2: // 标志字节为2，表示国家信息被重定向
		q.seekOffset(q.getRedirectOffset()) // 重定向地址
		country = q.readString(0)
		q.seekOffset(endIPOffset + 8)
		area = q.readArea()
	default:
		country = q.readString(mode)
		area = q.readArea()
	}
	countryUTF8, _ := enc.String(string(country))
	if strings.Trim(countryUTF8, " ") == "CZ88.NET" {
		res.Country = ""
	} else {
		res.Country = countryUTF8
	}
	areaUTF8, _ := enc.String(string(area))
	if strings.Trim(areaUTF8, " ") == "CZ88.NET" {
		res.Area = ""
	} else {
		res.Area = areaUTF8
	}
	return &res, nil
}

func (q *IPDict) findIndex(ip string) uint32 {
	if false == checkIPv4(ip) {
		return 0
	}
	if nil == q.fileData {
		err := q.Load(DefaultDict)
		if nil != err {
			return 0
		}
	}
	uIP := ip2long(ip)        // 将输入的IP地址转化为可比较的IP地址
	min := uint32(0)          // 搜索的下边界
	max := q.totalIPNum       // 搜索的上边界
	findIndex := q.lastOffset // 如果没有找到就返回最后一条IP记录（IPDict.Dat的版本信息）
	for min <= max {
		// 当上边界小于下边界时，查找失败
		mid := (min + max) / 2 // 计算近似中间记录
		q.seekOffset(q.firstOffset + mid*IndexLen)
		cBeginIP := q.getIPLong4() // 获取中间记录的开始IP地址
		if uIP < cBeginIP {        // 用户的IP小于中间记录的开始IP地址时
			max = mid - 1 // 将搜索的上边界修改为中间记录减一
		} else {
			q.seekOffset(q.getRedirectOffset())
			cEndIP := q.getIPLong4() // 获取中间记录的开始IP地址
			if uIP > cEndIP {        // 用户的IP大于中间记录的结束IP地址时
				min = mid + 1 // 将搜索的下边界修改为中间记录加一
			} else {
				// 用户的IP在中间记录的IP范围内时
				findIndex = q.firstOffset + mid*IndexLen
				break // 则表示找到结果，退出循环
			}
		}
	}
	return findIndex
}

//模拟文件读取Seek
func (q *IPDict) seekOffset(offset uint32) {
	q.offset = offset
}

//模拟文件读取Read
func (q *IPDict) readBuf(length uint32) []byte {
	q.offset = q.offset + length
	return q.fileData[q.offset-length : q.offset] // 标志字节
}

//返回读取的长整型数
func (q *IPDict) getIPLong4() uint32 {
	buf := q.readBuf(4)
	return binary.LittleEndian.Uint32(buf)
}

//返回读取的3个字节的长整型数
func (q *IPDict) getRedirectOffset() uint32 {
	buf := q.readBuf(3)
	return binary.LittleEndian.Uint32([]byte{buf[0], buf[1], buf[2], 0})
}

// readString 获取字符
func (q *IPDict) readMode() byte {
	return q.readBuf(1)[0] // 标志字节
}

// readString 获取字符串
func (q *IPDict) readString(char byte) []byte {
	data := make([]byte, 0, 30)
	if char != 0 {
		data = append(data, char)
	}
	buf := q.readBuf(1)
	for buf[0] != 0 {
		data = append(data, buf[0])
		buf = q.readBuf(1)
	}
	return data
}

// readArea 获取地区字符串
func (q *IPDict) readArea() []byte {
	mode := q.readMode()
	switch mode { // 标志字节
	case 0: // 结束标识
		return []byte{}
	case RedirectMode1:
	case RedirectMode2: // 标志字节为1或2，表示区域信息被重定向
		q.seekOffset(q.getRedirectOffset()) // 重定向地址
		return q.readString(0)
	}
	return q.readString(mode)
}

//数值IP转换字符串IP
func long2ip(ipInt uint32) string {
	// need to do two bit shifting and “0xff” masking
	ipInt64 := int64(ipInt)
	b0 := strconv.FormatInt((ipInt64>>24)&0xff, 10)
	b1 := strconv.FormatInt((ipInt64>>16)&0xff, 10)
	b2 := strconv.FormatInt((ipInt64>>8)&0xff, 10)
	b3 := strconv.FormatInt(ipInt64&0xff, 10)
	return b0 + "." + b1 + "." + b2 + "." + b3
}

//字符串IP转换数值IP
func ip2long(ip string) uint32 {
	bIP := net.ParseIP(ip).To4()
	if nil == bIP {
		return 0
	}
	return binary.BigEndian.Uint32(bIP)
}

//返回字典绝对路径
func dictPath(dictFileName string) (string, error) {
	if filepath.IsAbs(dictFileName) {
		return dictFileName, nil
	}
	var dictFilePath string
	cwd, err := os.Getwd()
	if err != nil {
		return dictFilePath, err
	}
	dictFilePath = filepath.Clean(filepath.Join(cwd, dictFileName))
	return dictFilePath, nil
}

//检查ip地址
func checkIPv4(IP string) bool {
	// 字符串这样切割
	strList := strings.Split(IP, ".")
	if len(strList) != 4 {
		return false
	}
	for _, s := range strList {
		if len(s) == 0 || (len(s) > 1 && s[0] == '0') {
			return false
		}
		// 直接访问字符串的值
		if s[0] < '0' || s[0] > '9' {
			return false
		}
		// 字符串转数字
		n, err := strconv.Atoi(s)
		if err != nil {
			return false
		}
		if n < 0 || n > 255 {
			return false
		}
	}
	return true
}

func GetIpAddress(ip string) (string, error) {
	IPDict := NewIPDict()
	//载入IP字典
	err := IPDict.Load("../conf/qqwry.dat")
	if err != nil {
		return "", err
	}
	//查询IP
	res, err := IPDict.FindIP(ip)
	if err != nil {
		return "", err
	}
	return res.Country, nil
}

func GetIpOnlyAddress(ip string) string {
	IPDict := NewIPDict()
	//载入IP字典
	err := IPDict.Load("../conf/qqwry.dat")
	if err != nil {
		return ""
	}
	//查询IP
	res, err := IPDict.FindIP(ip)
	if err != nil {
		return ""
	}
	return res.Country
}

func AliIpAddress(ip string) (string, error) {

	url := "https://c2ba.api.huachen.cn/ip?ip=" + ip

	client := &http.Client{
		Timeout: 1 * time.Second,
	}
	//提交请求
	reqest, err := http.NewRequest("GET", url, nil)

	//增加header选项
	reqest.Header.Add("Authorization", "APPCODE 89aaef07254149aabac798c911647673")

	if err != nil {
		fmt.Println(err)
		return "", err
		//panic(err)
	}
	//处理返回结果
	response, err := client.Do(reqest)
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	resByte, err := ioutil.ReadAll(response.Body)

	fmt.Println("1-------------", string(resByte))
	var result IpResultV2
	err = json.Unmarshal(resByte, &result)
	fmt.Printf("1-------------%+v\n", result)

	defer response.Body.Close()

	return result.Result.Country + result.Result.City, nil
}

func GetIpOnlyAddressFromAli(ip string) string {
	IPDict := NewIPDict()
	//载入IP字典
	err := IPDict.Load("../conf/qqwry.dat")
	if err != nil {
		return ""
	}
	//查询IP
	res, err := IPDict.FindIP(ip)
	if err != nil {
		return ""
	}
	return res.Country
}
