package YaDisk

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/yandex"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

const (
	BASE_URL  = "https://cloud-api.yandex.net"
	OAUTH_URL = "https://oauth.yandex.ru"
)

type Client struct {
	App        *ClientApp
	HTTPClient *http.Client
	Token      *oauth2.Token
}

type ClientApp struct {
	Config     *oauth2.Config
	ServiceUrl string
}

func NewClientApp(clientID, clientSecret, redirectUri, serviceUrl string) *ClientApp {
	return &ClientApp{
		Config: &oauth2.Config{
			ClientID:     clientID,
			ClientSecret: clientSecret,
			Endpoint:     yandex.Endpoint,
			RedirectURL:  redirectUri,
			Scopes:       nil,
		},
		ServiceUrl: serviceUrl,
	}
}

func (ca *ClientApp) GenerateOAuthUrl() string {
	return ca.Config.AuthCodeURL(
		"state-token",
		oauth2.AccessTypeOffline,
		oauth2.SetAuthURLParam("display", "popup"),
		oauth2.SetAuthURLParam("force_confirm", "true"),
	)
}

func (ca *ClientApp) NewClient(ctx context.Context, token *oauth2.Token, code string) (*Client, error) {
	var err error
	if code != "" {
		token, err = ca.Config.Exchange(ctx, code)
	}

	// Этот костыль нужен, т.к. Яндекс принимает токены только типа OAuth, хоть и отправляет типа bearer
	token.TokenType = "OAuth"

	return &Client{
		App:        ca,
		HTTPClient: ca.Config.Client(ctx, token),
		Token:      token,
	}, err
}

func (ca *ClientApp) RefreshToken(ctx context.Context, oldToken *oauth2.Token) (*oauth2.Token, error) {
	token, err := ca.Config.TokenSource(ctx, oldToken).Token()
	if err != nil {
		return nil, err
	}

	// Этот костыль нужен, т.к. Яндекс принимает токены только типа OAuth, хоть и отправляет типа bearer
	token.TokenType = "OAuth"

	return token, nil
}

func (c *Client) GetDisk() (*Disk, error) {
	req, err := http.NewRequest("GET", BASE_URL+"/v1/disk", nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.HTTPClient.Do(req)

	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		r := Error{}
		json.NewDecoder(resp.Body).Decode(&r)
		return nil, errors.New(fmt.Sprintf("api/yaDisk err: %v (%v)", r.Description, r.Error))
	}

	r := Disk{}
	err = json.NewDecoder(resp.Body).Decode(&r)
	if err != nil {
		return nil, err
	}

	return &r, nil
}

func (c *Client) GetUser() (*User, error) {
	disk, err := c.GetDisk()
	if err != nil {
		return nil, err
	}

	return &disk.User, nil
}

func (c *Client) GetResources(path string) (*Resource, error) {
	data := url.Values{}
	data.Set("path", path)
	req, err := http.NewRequest("GET", BASE_URL+"/v1/disk/resources?path="+path, nil)

	if err != nil {
		return nil, err
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode == 404 {
		fmt.Println("api/yaDisk: 404")
		return nil, nil
	} else if resp.StatusCode != 200 {
		r := Error{}
		err = json.NewDecoder(resp.Body).Decode(&r)
		if err != nil {
			return nil, err
		}
		return nil, errors.New(fmt.Sprintf("api/yaDisk err: %v (%v)", r.Description, r.Error))
	}

	r := Resource{}
	err = json.NewDecoder(resp.Body).Decode(&r)

	return &r, err
}

func (c *Client) DownloadResource(path, downloadPath string) error {
	res, err := c.GetResources(path)
	if err != nil {
		return err
	}

	resp, err := http.Get(res.File)

	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Create the file
	out, err := os.Create(downloadPath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Write the body to file
	_, err = io.Copy(out, resp.Body)
	return err
}

func (c *Client) PutResources(path string) error {
	data := url.Values{}
	data.Set("path", path)
	req, err := http.NewRequest("PUT", BASE_URL+"/v1/disk/resources?"+data.Encode(), nil)
	if err != nil {
		return err
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}

	r := map[string]interface{}{}

	err = json.NewDecoder(resp.Body).Decode(&r)

	fmt.Printf("%+v", r)

	return err
}

func (c *Client) ResourcesUpload(path, downloadPath string) error {
	downloadPath = strings.TrimLeft(downloadPath, "..")
	downloadPath = strings.TrimLeft(downloadPath, ".")

	data := url.Values{}
	data.Set("path", path)
	data.Set("url", c.App.ServiceUrl+downloadPath)
	req, err := http.NewRequest("POST", BASE_URL+"/v1/disk/resources/upload?"+data.Encode(), nil)
	if err != nil {
		return err
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}

	r := map[string]interface{}{}

	err = json.NewDecoder(resp.Body).Decode(&r)

	fmt.Printf("%+v", r)

	return err
}
