package baiduphoto

import (
	"context"
	"encoding/hex"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"unicode"

	"github.com/AlliotTech/openalist/drivers/base"
	"github.com/AlliotTech/openalist/internal/model"
	"github.com/AlliotTech/openalist/pkg/utils"
	"github.com/go-resty/resty/v2"
)

const (
	API_URL         = "https://photo.baidu.com/youai"
	USER_API_URL    = API_URL + "/user/v1"
	ALBUM_API_URL   = API_URL + "/album/v1"
	FILE_API_URL_V1 = API_URL + "/file/v1"
	FILE_API_URL_V2 = API_URL + "/file/v2"
)

func (d *BaiduPhoto) Request(client *resty.Client, furl string, method string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
	req := client.R().
		// SetQueryParam("access_token", d.AccessToken)
		SetHeader("Cookie", d.Cookie)
	if callback != nil {
		callback(req)
	}
	if resp != nil {
		req.SetResult(resp)
	}
	res, err := req.Execute(method, furl)
	if err != nil {
		return nil, err
	}

	erron := utils.Json.Get(res.Body(), "errno").ToInt()
	switch erron {
	case 0:
		break
	case 50805:
		return nil, fmt.Errorf("you have joined album")
	case 50820:
		return nil, fmt.Errorf("no shared albums found")
	case 50100:
		return nil, fmt.Errorf("illegal title, only supports 50 characters")
	// case -6:
	// 	if err = d.refreshToken(); err != nil {
	// 		return nil, err
	// 	}
	default:
		return nil, fmt.Errorf("errno: %d, refer to https://photo.baidu.com/union/doc", erron)
	}
	return res, nil
}

//func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
//	res, err := d.request(furl, method, callback, resp)
//	if err != nil {
//		return nil, err
//	}
//	return res.Body(), nil
//}

// func (d *BaiduPhoto) refreshToken() error {
// 	u := "https://openapi.baidu.com/oauth/2.0/token"
// 	var resp base.TokenResp
// 	var e TokenErrResp
// 	_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
// 		"grant_type":    "refresh_token",
// 		"refresh_token": d.RefreshToken,
// 		"client_id":     d.ClientID,
// 		"client_secret": d.ClientSecret,
// 	}).Get(u)
// 	if err != nil {
// 		return err
// 	}
// 	if e.ErrorMsg != "" {
// 		return &e
// 	}
// 	if resp.RefreshToken == "" {
// 		return errs.EmptyToken
// 	}
// 	d.AccessToken, d.RefreshToken = resp.AccessToken, resp.RefreshToken
// 	op.MustSaveDriverStorage(d)
// 	return nil
// }

func (d *BaiduPhoto) Get(furl string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
	return d.Request(base.RestyClient, furl, http.MethodGet, callback, resp)
}

func (d *BaiduPhoto) Post(furl string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
	return d.Request(base.RestyClient, furl, http.MethodPost, callback, resp)
}

// 获取所有文件
func (d *BaiduPhoto) GetAllFile(ctx context.Context) (files []File, err error) {
	var cursor string
	for {
		var resp FileListResp
		_, err = d.Get(FILE_API_URL_V1+"/list", func(r *resty.Request) {
			r.SetContext(ctx)
			r.SetQueryParams(map[string]string{
				"need_thumbnail":     "1",
				"need_filter_hidden": "0",
				"cursor":             cursor,
			})
		}, &resp)
		if err != nil {
			return
		}

		files = append(files, resp.List...)
		if !resp.HasNextPage() {
			return
		}
		cursor = resp.Cursor
	}
}

// 删除根文件
func (d *BaiduPhoto) DeleteFile(ctx context.Context, file *File) error {
	_, err := d.Get(FILE_API_URL_V1+"/delete", func(req *resty.Request) {
		req.SetContext(ctx)
		req.SetQueryParams(map[string]string{
			"fsid_list": fmt.Sprintf("[%d]", file.Fsid),
		})
	}, nil)
	return err
}

// 获取所有相册
func (d *BaiduPhoto) GetAllAlbum(ctx context.Context) (albums []Album, err error) {
	var cursor string
	for {
		var resp AlbumListResp
		_, err = d.Get(ALBUM_API_URL+"/list", func(r *resty.Request) {
			r.SetContext(ctx)
			r.SetQueryParams(map[string]string{
				"need_amount": "1",
				"limit":       "100",
				"cursor":      cursor,
			})
		}, &resp)
		if err != nil {
			return
		}
		if albums == nil {
			albums = make([]Album, 0, resp.TotalCount)
		}

		cursor = resp.Cursor
		albums = append(albums, resp.List...)

		if !resp.HasNextPage() {
			return
		}
	}
}

// 获取相册中所有文件
func (d *BaiduPhoto) GetAllAlbumFile(ctx context.Context, album *Album, passwd string) (files []AlbumFile, err error) {
	var cursor string
	for {
		var resp AlbumFileListResp
		_, err = d.Get(ALBUM_API_URL+"/listfile", func(r *resty.Request) {
			r.SetContext(ctx)
			r.SetQueryParams(map[string]string{
				"album_id":    album.AlbumID,
				"need_amount": "1",
				"limit":       "1000",
				"passwd":      passwd,
				"cursor":      cursor,
			})
		}, &resp)
		if err != nil {
			return
		}
		if files == nil {
			files = make([]AlbumFile, 0, resp.TotalCount)
		}

		cursor = resp.Cursor
		files = append(files, resp.List...)

		if !resp.HasNextPage() {
			return
		}
	}
}

// 创建相册
func (d *BaiduPhoto) CreateAlbum(ctx context.Context, name string) (*Album, error) {
	var resp JoinOrCreateAlbumResp
	_, err := d.Post(ALBUM_API_URL+"/create", func(r *resty.Request) {
		r.SetContext(ctx).SetResult(&resp)
		r.SetQueryParams(map[string]string{
			"title":  name,
			"tid":    getTid(),
			"source": "0",
		})
	}, nil)
	if err != nil {
		return nil, err
	}
	return d.GetAlbumDetail(ctx, resp.AlbumID)
}

// 相册改名
func (d *BaiduPhoto) SetAlbumName(ctx context.Context, album *Album, name string) (*Album, error) {
	_, err := d.Post(ALBUM_API_URL+"/settitle", func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetFormData(map[string]string{
			"title":    name,
			"album_id": album.AlbumID,
			"tid":      fmt.Sprint(album.Tid),
		})
	}, nil)
	if err != nil {
		return nil, err
	}
	return renameAlbum(album, name), nil
}

// 删除相册
func (d *BaiduPhoto) DeleteAlbum(ctx context.Context, album *Album) error {
	_, err := d.Post(ALBUM_API_URL+"/delete", func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetFormData(map[string]string{
			"album_id":            album.AlbumID,
			"tid":                 fmt.Sprint(album.Tid),
			"delete_origin_image": BoolToIntStr(d.DeleteOrigin), // 是否删除原图 0 不删除 1 删除
		})
	}, nil)
	return err
}

// 删除相册文件
func (d *BaiduPhoto) DeleteAlbumFile(ctx context.Context, file *AlbumFile) error {
	_, err := d.Post(ALBUM_API_URL+"/delfile", func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetFormData(map[string]string{
			"album_id":   fmt.Sprint(file.AlbumID),
			"tid":        fmt.Sprint(file.Tid),
			"list":       fmt.Sprintf(`[{"fsid":%d,"uk":%d}]`, file.Fsid, file.Uk),
			"del_origin": BoolToIntStr(d.DeleteOrigin), // 是否删除原图 0 不删除 1 删除
		})
	}, nil)
	return err
}

// 增加相册文件
func (d *BaiduPhoto) AddAlbumFile(ctx context.Context, album *Album, file *File) (*AlbumFile, error) {
	_, err := d.Get(ALBUM_API_URL+"/addfile", func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetQueryParams(map[string]string{
			"album_id": fmt.Sprint(album.AlbumID),
			"tid":      fmt.Sprint(album.Tid),
			"list":     fsidsFormatNotUk(file.Fsid),
		})
	}, nil)
	if err != nil {
		return nil, err
	}
	return moveFileToAlbumFile(file, album, d.Uk), nil
}

// 保存相册文件为根文件
func (d *BaiduPhoto) CopyAlbumFile(ctx context.Context, file *AlbumFile) (*File, error) {
	var resp CopyFileResp
	_, err := d.Post(ALBUM_API_URL+"/copyfile", func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetFormData(map[string]string{
			"album_id": file.AlbumID,
			"tid":      fmt.Sprint(file.Tid),
			"uk":       fmt.Sprint(file.Uk),
			"list":     fsidsFormatNotUk(file.Fsid),
		})
		r.SetResult(&resp)
	}, nil)
	if err != nil {
		return nil, err
	}
	return copyFile(file, &resp.List[0]), nil
}

// 加入相册
func (d *BaiduPhoto) JoinAlbum(ctx context.Context, code string) (*Album, error) {
	var resp InviteResp
	_, err := d.Get(ALBUM_API_URL+"/querypcode", func(req *resty.Request) {
		req.SetContext(ctx)
		req.SetQueryParams(map[string]string{
			"pcode": code,
			"web":   "1",
		})
	}, &resp)
	if err != nil {
		return nil, err
	}
	var resp2 JoinOrCreateAlbumResp
	_, err = d.Get(ALBUM_API_URL+"/join", func(req *resty.Request) {
		req.SetContext(ctx)
		req.SetQueryParams(map[string]string{
			"invite_code": resp.Pdata.InviteCode,
		})
	}, &resp2)
	if err != nil {
		return nil, err
	}
	return d.GetAlbumDetail(ctx, resp2.AlbumID)
}

// 获取相册详细信息
func (d *BaiduPhoto) GetAlbumDetail(ctx context.Context, albumID string) (*Album, error) {
	var album Album
	_, err := d.Get(ALBUM_API_URL+"/detail", func(req *resty.Request) {
		req.SetContext(ctx).SetResult(&album)
		req.SetQueryParams(map[string]string{
			"album_id": albumID,
		})
	}, &album)
	if err != nil {
		return nil, err
	}
	return &album, nil
}

func (d *BaiduPhoto) linkAlbum(ctx context.Context, file *AlbumFile, args model.LinkArgs) (*model.Link, error) {
	headers := map[string]string{
		"User-Agent": base.UserAgent,
	}
	if args.Header.Get("User-Agent") != "" {
		headers["User-Agent"] = args.Header.Get("User-Agent")
	}
	if !utils.IsLocalIPAddr(args.IP) {
		headers["X-Forwarded-For"] = args.IP
	}

	resp, err := d.Request(base.NoRedirectClient, ALBUM_API_URL+"/download", http.MethodHead, func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetHeaders(headers)
		r.SetQueryParams(map[string]string{
			"fsid":     fmt.Sprint(file.Fsid),
			"album_id": file.AlbumID,
			"tid":      fmt.Sprint(file.Tid),
			"uk":       fmt.Sprint(file.Uk),
		})
	}, nil)

	if err != nil {
		return nil, err
	}

	if resp.StatusCode() != 302 {
		return nil, fmt.Errorf("not found 302 redirect")
	}

	location := resp.Header().Get("Location")

	link := &model.Link{
		URL: location,
		Header: http.Header{
			"User-Agent": []string{headers["User-Agent"]},
			"Referer":    []string{"https://photo.baidu.com/"},
		},
	}
	return link, nil
}

func (d *BaiduPhoto) linkFile(ctx context.Context, file *File, args model.LinkArgs) (*model.Link, error) {
	headers := map[string]string{
		"User-Agent": base.UserAgent,
	}
	if args.Header.Get("User-Agent") != "" {
		headers["User-Agent"] = args.Header.Get("User-Agent")
	}
	if !utils.IsLocalIPAddr(args.IP) {
		headers["X-Forwarded-For"] = args.IP
	}

	var downloadUrl struct {
		Dlink string `json:"dlink"`
	}
	_, err := d.Get(FILE_API_URL_V2+"/download", func(r *resty.Request) {
		r.SetContext(ctx)
		r.SetHeaders(headers)
		r.SetQueryParams(map[string]string{
			"fsid": fmt.Sprint(file.Fsid),
		})
	}, &downloadUrl)

	// resp, err := d.Request(base.NoRedirectClient, FILE_API_URL_V1+"/download", http.MethodHead, func(r *resty.Request) {
	// 	r.SetContext(ctx)
	// 	r.SetHeaders(headers)
	// 	r.SetQueryParams(map[string]string{
	// 		"fsid": fmt.Sprint(file.Fsid),
	// 	})
	// }, nil)

	if err != nil {
		return nil, err
	}

	// if resp.StatusCode() != 302 {
	// 	return nil, fmt.Errorf("not found 302 redirect")
	// }

	// location := resp.Header().Get("Location")
	link := &model.Link{
		URL: downloadUrl.Dlink,
		Header: http.Header{
			"User-Agent": []string{headers["User-Agent"]},
			"Referer":    []string{"https://photo.baidu.com/"},
		},
	}
	return link, nil
}

/*func (d *BaiduPhoto) linkStreamAlbum(ctx context.Context, file *AlbumFile) (*model.Link, error) {
	return &model.Link{
		Header: http.Header{},
		Writer: func(w io.Writer) error {
			res, err := d.Get(ALBUM_API_URL+"/streaming", func(r *resty.Request) {
				r.SetContext(ctx)
				r.SetQueryParams(map[string]string{
					"fsid":     fmt.Sprint(file.Fsid),
					"album_id": file.AlbumID,
					"tid":      fmt.Sprint(file.Tid),
					"uk":       fmt.Sprint(file.Uk),
				}).SetDoNotParseResponse(true)
			}, nil)
			if err != nil {
				return err
			}
			defer res.RawBody().Close()
			_, err = io.Copy(w, res.RawBody())
			return err
		},
	}, nil
}*/

/*func (d *BaiduPhoto) linkStream(ctx context.Context, file *File) (*model.Link, error) {
	return &model.Link{
		Header: http.Header{},
		Writer: func(w io.Writer) error {
			res, err := d.Get(FILE_API_URL_V1+"/streaming", func(r *resty.Request) {
				r.SetContext(ctx)
				r.SetQueryParams(map[string]string{
					"fsid": fmt.Sprint(file.Fsid),
				}).SetDoNotParseResponse(true)
			}, nil)
			if err != nil {
				return err
			}
			defer res.RawBody().Close()
			_, err = io.Copy(w, res.RawBody())
			return err
		},
	}, nil
}*/

// 获取uk
func (d *BaiduPhoto) uInfo() (*UInfo, error) {
	var info UInfo
	_, err := d.Get(USER_API_URL+"/getuinfo", func(req *resty.Request) {

	}, &info)
	if err != nil {
		return nil, err
	}
	return &info, nil
}

func (d *BaiduPhoto) getBDStoken() (string, error) {
	var info struct {
		Result struct {
			Bdstoken string `json:"bdstoken"`
			Token    string `json:"token"`
			Uk       int64  `json:"uk"`
		} `json:"result"`
	}
	_, err := d.Get("https://pan.baidu.com/api/gettemplatevariable?fields=[%22bdstoken%22,%22token%22,%22uk%22]", nil, &info)
	if err != nil {
		return "", err
	}
	return info.Result.Bdstoken, nil
}

func DecryptMd5(encryptMd5 string) string {
	if _, err := hex.DecodeString(encryptMd5); err == nil {
		return encryptMd5
	}

	var out strings.Builder
	out.Grow(len(encryptMd5))
	for i, n := 0, int64(0); i < len(encryptMd5); i++ {
		if i == 9 {
			n = int64(unicode.ToLower(rune(encryptMd5[i])) - 'g')
		} else {
			n, _ = strconv.ParseInt(encryptMd5[i:i+1], 16, 64)
		}
		out.WriteString(strconv.FormatInt(n^int64(15&i), 16))
	}

	encryptMd5 = out.String()
	return encryptMd5[8:16] + encryptMd5[:8] + encryptMd5[24:32] + encryptMd5[16:24]
}

func EncryptMd5(originalMd5 string) string {
	reversed := originalMd5[8:16] + originalMd5[:8] + originalMd5[24:32] + originalMd5[16:24]

	var out strings.Builder
	out.Grow(len(reversed))
	for i, n := 0, int64(0); i < len(reversed); i++ {
		n, _ = strconv.ParseInt(reversed[i:i+1], 16, 64)
		n ^= int64(15 & i)
		if i == 9 {
			out.WriteRune(rune(n) + 'g')
		} else {
			out.WriteString(strconv.FormatInt(n, 16))
		}
	}
	return out.String()
}
