package baidu_share

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
	"time"

	"github.com/AlliotTech/openalist/internal/driver"
	"github.com/AlliotTech/openalist/internal/errs"
	"github.com/AlliotTech/openalist/internal/model"
	"github.com/go-resty/resty/v2"
)

type BaiduShare struct {
	model.Storage
	Addition
	client *resty.Client
	info   struct {
		Root    string
		Seckey  string
		Shareid string
		Uk      string
	}
}

func (d *BaiduShare) Config() driver.Config {
	return config
}

func (d *BaiduShare) GetAddition() driver.Additional {
	return &d.Addition
}

func (d *BaiduShare) Init(ctx context.Context) error {
	// TODO login / refresh token
	//op.MustSaveDriverStorage(d)
	d.client = resty.New().
		SetBaseURL("https://pan.baidu.com").
		SetHeader("User-Agent", "netdisk").
		SetCookie(&http.Cookie{Name: "BDUSS", Value: d.BDUSS}).
		SetCookie(&http.Cookie{Name: "ndut_fmt"})
	respJson := struct {
		Errno int64 `json:"errno"`
		Data  struct {
			List [1]struct {
				Path string `json:"path"`
			} `json:"list"`
			Uk      json.Number `json:"uk"`
			Shareid json.Number `json:"shareid"`
			Seckey  string      `json:"seckey"`
		} `json:"data"`
	}{}
	resp, err := d.client.R().
		SetBody(url.Values{
			"pwd":      {d.Pwd},
			"root":     {"1"},
			"shorturl": {d.Surl},
		}.Encode()).
		SetResult(&respJson).
		Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
	if err == nil {
		if resp.IsSuccess() && respJson.Errno == 0 {
			d.info.Root = path.Dir(respJson.Data.List[0].Path)
			d.info.Seckey = respJson.Data.Seckey
			d.info.Shareid = respJson.Data.Shareid.String()
			d.info.Uk = respJson.Data.Uk.String()
		} else {
			err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
		}
	}
	return err
}

func (d *BaiduShare) Drop(ctx context.Context) error {
	return nil
}

func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
	// TODO return the files list, required
	reqDir := dir.GetPath()
	isRoot := "0"
	if reqDir == d.RootFolderPath {
		reqDir = path.Join(d.info.Root, reqDir)
	}
	if reqDir == d.info.Root {
		isRoot = "1"
	}
	objs := []model.Obj{}
	var err error
	var page uint64 = 1
	more := true
	for more && err == nil {
		respJson := struct {
			Errno int64 `json:"errno"`
			Data  struct {
				More bool `json:"has_more"`
				List []struct {
					Fsid  json.Number `json:"fs_id"`
					Isdir json.Number `json:"isdir"`
					Path  string      `json:"path"`
					Name  string      `json:"server_filename"`
					Mtime json.Number `json:"server_mtime"`
					Size  json.Number `json:"size"`
				} `json:"list"`
			} `json:"data"`
		}{}
		resp, e := d.client.R().
			SetBody(url.Values{
				"dir":      {reqDir},
				"num":      {"1000"},
				"order":    {"time"},
				"page":     {fmt.Sprint(page)},
				"pwd":      {d.Pwd},
				"root":     {isRoot},
				"shorturl": {d.Surl},
			}.Encode()).
			SetResult(&respJson).
			Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
		err = e
		if err == nil {
			if resp.IsSuccess() && respJson.Errno == 0 {
				page++
				more = respJson.Data.More
				for _, v := range respJson.Data.List {
					size, _ := v.Size.Int64()
					mtime, _ := v.Mtime.Int64()
					objs = append(objs, &model.Object{
						ID:       v.Fsid.String(),
						Path:     v.Path,
						Name:     v.Name,
						Size:     size,
						Modified: time.Unix(mtime, 0),
						IsFolder: v.Isdir.String() == "1",
					})
				}
			} else {
				err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
			}
		}
	}
	return objs, err
}

func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	// TODO return link of file, required
	link := model.Link{Header: d.client.Header}
	sign := ""
	stamp := ""
	signJson := struct {
		Errno int64 `json:"errno"`
		Data  struct {
			Stamp json.Number `json:"timestamp"`
			Sign  string      `json:"sign"`
		} `json:"data"`
	}{}
	resp, err := d.client.R().
		SetQueryParam("surl", d.Surl).
		SetResult(&signJson).
		Get("share/tplconfig?fields=sign,timestamp&channel=chunlei&web=1&app_id=250528&clienttype=0")
	if err == nil {
		if resp.IsSuccess() && signJson.Errno == 0 {
			stamp = signJson.Data.Stamp.String()
			sign = signJson.Data.Sign
		} else {
			err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
		}
	}
	if err == nil {
		respJson := struct {
			Errno int64 `json:"errno"`
			List  [1]struct {
				Dlink string `json:"dlink"`
			} `json:"list"`
		}{}
		resp, err = d.client.R().
			SetQueryParam("sign", sign).
			SetQueryParam("timestamp", stamp).
			SetBody(url.Values{
				"encrypt":   {"0"},
				"extra":     {fmt.Sprintf(`{"sekey":"%s"}`, d.info.Seckey)},
				"fid_list":  {fmt.Sprintf("[%s]", file.GetID())},
				"primaryid": {d.info.Shareid},
				"product":   {"share"},
				"type":      {"nolimit"},
				"uk":        {d.info.Uk},
			}.Encode()).
			SetResult(&respJson).
			Post("api/sharedownload?app_id=250528&channel=chunlei&clienttype=12&web=1")
		if err == nil {
			if resp.IsSuccess() && respJson.Errno == 0 && respJson.List[0].Dlink != "" {
				link.URL = respJson.List[0].Dlink
			} else {
				err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
			}
		}
		if err == nil {
			resp, err = d.client.R().
				SetDoNotParseResponse(true).
				Get(link.URL)
			if err == nil {
				defer resp.RawBody().Close()
				if resp.IsError() {
					byt, _ := io.ReadAll(resp.RawBody())
					err = fmt.Errorf(" %s; %s; ", resp.Status(), byt)
				}
			}
		}
	}
	return &link, err
}

func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
	// TODO create folder, optional
	return errs.NotSupport
}

func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
	// TODO move obj, optional
	return errs.NotSupport
}

func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
	// TODO rename obj, optional
	return errs.NotSupport
}

func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
	// TODO copy obj, optional
	return errs.NotSupport
}

func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
	// TODO remove obj, optional
	return errs.NotSupport
}

func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
	// TODO upload file, optional
	return errs.NotSupport
}

//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
//	return nil, errs.NotSupport
//}

var _ driver.Driver = (*BaiduShare)(nil)
