package baiduphoto

import (
	"context"
	"errors"
	"fmt"
	"net/http"

	"github.com/alist-org/alist/v3/drivers/base"
	"github.com/alist-org/alist/v3/internal/errs"
	"github.com/alist-org/alist/v3/internal/model"
	"github.com/alist-org/alist/v3/internal/op"
	"github.com/alist-org/alist/v3/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"
)

var (
	ErrNotSupportName = errors.New("only chinese and english, numbers and underscores are supported, and the length is no more than 20")
)

func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
	req := base.RestyClient.R().
		SetQueryParam("access_token", d.AccessToken)
	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 -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.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{}) ([]byte, error) {
	return d.Request(furl, http.MethodGet, callback, resp)
}

func (d *BaiduPhoto) Post(furl string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
	return d.Request(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) {
	if !checkName(name) {
		return nil, ErrNotSupportName
	}
	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) {
	if !checkName(name) {
		return nil, ErrNotSupportName
	}

	_, 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": "0", // 是否删除原图 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": "0", // 是否删除原图 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
	}

	res, err := base.NoRedirectClient.R().
		SetContext(ctx).
		SetHeaders(headers).
		SetQueryParams(map[string]string{
			"access_token": d.AccessToken,
			"fsid":         fmt.Sprint(file.Fsid),
			"album_id":     file.AlbumID,
			"tid":          fmt.Sprint(file.Tid),
			"uk":           fmt.Sprint(file.Uk),
		}).
		Head(ALBUM_API_URL + "/download")

	if err != nil {
		return nil, err
	}

	link := &model.Link{
		URL: res.Header().Get("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)
	if err != nil {
		return nil, err
	}

	link := &model.Link{
		URL: downloadUrl.Dlink,
		Header: http.Header{
			"User-Agent": []string{headers["User-Agent"]},
			"Referer":    []string{"https://photo.baidu.com/"},
		},
	}
	return link, 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
}
