package GDisk

import (
	"context"
	"errors"
	"fmt"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/drive/v3"
	"google.golang.org/api/option"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

const (
	DefaultDir  = "TemplateGenerator"
	TemplateDir = "templates"
	SaveDir     = "saved"
)

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

type ClientApp struct {
	Config *oauth2.Config
}

func NewClientApp(credentials string) (*ClientApp, error) {
	b, err := ioutil.ReadFile(credentials)
	if err != nil {
		return nil, err
	}

	config, err := google.ConfigFromJSON(b,
		drive.DriveScope,
		drive.DriveMetadataScope,
		drive.DriveFileScope,
		drive.DriveAppdataScope,
	)
	if err != nil {
		return nil, err
	}

	return &ClientApp{Config: config}, nil
}

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

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
	}

	return token, nil
}

func (ca *ClientApp) NewClient(ctx context.Context, token *oauth2.Token) (*Client, error) {
	client := ca.Config.Client(ctx, token)
	srv, err := drive.NewService(ctx, option.WithHTTPClient(client))
	if err != nil {
		return nil, err
	}

	return &Client{
		App:        ca,
		HTTPClient: client,
		Service:    srv,
		Token:      token,
	}, nil
}

func (ca *ClientApp) GetToken(ctx context.Context, authCode string) (*oauth2.Token, error) {
	return ca.Config.Exchange(ctx, authCode)
}

func (c *Client) SetToken(token oauth2.Token) {
	c.Token = &token
}

func (c *Client) GetDisk() {
	srv, err := drive.NewService(context.Background(), option.WithHTTPClient(c.HTTPClient))

	if err != nil {
		fmt.Println("err123", err)
		return
	}

	about, err := srv.About.Get().Fields("storageQuota", "user", "exportFormats", "maxImportSizes", "maxUploadSize").Do()
	if err != nil {
		fmt.Println("err1234", err)
		return
	}
	fmt.Printf("srv.About %+v \r\n", about.User)

	dl, err := srv.Drives.List().Do()
	if err != nil {
		fmt.Println("err12345", err)
		return
	}

	fmt.Println("DRIVES ________________________")
	for _, drive := range dl.Drives {
		fmt.Println(drive)
	}
}

// GetUserInfo - получить информацию о пользователе Google
func (c *Client) GetUserInfo() (*drive.User, error) {
	srv, err := drive.NewService(context.Background(), option.WithHTTPClient(c.HTTPClient))

	if err != nil {
		return nil, err
	}

	about, err := srv.About.Get().Fields("user").Do()

	if err != nil {
		return nil, err
	}

	if about == nil {
		return nil, errors.New("got empty about")
	}

	if about.User == nil {
		return nil, errors.New("got empty about.User")
	}

	return about.User, nil
}

// GetResourceByName - получить информацию о ресурсе
func (c *Client) GetResourceByName(name, parentID string) ([]*drive.File, error) {
	query := "name = '" + name + "' and trashed = false"

	if parentID != "" {
		query += "and '" + parentID + "' in parents"
	}

	fl, err := c.Service.Files.List().
		Q(query).
		Do()
	if err != nil {
		if strings.Contains(err.Error(), "404") {
			return nil, nil
		}
		fmt.Println("1", err.Error())
		return nil, err
	}

	if len(fl.Files) == 0 {
		return nil, nil
	}

	return fl.Files, nil
}

// GetResources - получить список файлов и папок ресурса по его id
func (c *Client) GetResources(id string) ([]*drive.File, error) {
	query := fmt.Sprintf("trashed = false and '%v' in parents", id)

	fl, err := c.Service.Files.List().
		Q(query).
		Do()
	if err != nil {
		if strings.Contains(err.Error(), "404") {
			return nil, nil
		}
		fmt.Println("2", err.Error())
		return nil, err
	}

	if len(fl.Files) == 0 {
		return nil, nil
	}

	return fl.Files, nil
}

// PutResources - создание папки
func (c *Client) PutResources(name, parentID string) (*drive.File, error) {
	q := &drive.File{Name: name, MimeType: "application/vnd.google-apps.folder"}
	if parentID != "" {
		q.Parents = []string{parentID}
	}
	f, err := c.Service.Files.Create(q).Do()

	if err != nil {
		fmt.Println("3", err.Error())
		fmt.Println("")
		return nil, err
	}

	return f, nil
}

// UploadFile - отправить файл в диск
func (c *Client) UploadFile(filepath, mimetype, parentID string) error {
	file, err := os.Open(filepath)
	defer file.Close()

	if err != nil {
		return err
	}

	filename := strings.Split(file.Name(), "/")

	metaFile := drive.File{
		Name:     filename[len(filename)-1],
		MimeType: mimetype,
		Parents:  []string{parentID},
	}

	_, err = c.Service.Files.Create(&metaFile).Media(file).Do()

	if err != nil {
		return err
	}

	return nil
}

func (c *Client) DownloadFile(filepath, fileID string) error {
	file, err := c.Service.Files.Get(fileID).Do()
	if err != nil {
		return err
	}

	var resp *http.Response
	fmt.Println("mimeType", file.MimeType)
	if file.MimeType == "application/vnd.google-apps.document" {
		resp, err = c.Service.Files.Export(fileID, "application/vnd.openxmlformats-officedocument.wordprocessingml.document").
			Fields("exportFormat", "docx").
			Download()
	} else {
		resp, err = c.Service.Files.Get(fileID).Download()
	}

	if err != nil {
		return err
	}

	defer resp.Body.Close()

	out, err := os.Create(filepath)
	defer out.Close()

	if err != nil {
		return err
	}

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return err
	}

	return nil
}

// MakeDefaultDirs - проверяет диск на наличие папок DefaultDir, TemplateDir и SaveDir.
// Если папки TemplateDir и SaveDir отсутствуют создает их в DefaultDir и возвращает их id
func (c *Client) MakeDefaultDirs(templateId, saveId string) (string, string, error) {
	var defaultDir, templateDir, saveDir *drive.File

	// Check Default directory
	fl, err := c.GetResourceByName(DefaultDir, "")

	if err != nil {
		return "", "", err
	}

	if fl == nil {
		defaultDir, err = c.PutResources(DefaultDir, "")
		if err != nil {
			return "", "", err
		}
	} else {
		defaultDir = fl[0]
	}

	// Check Template Directory
	if templateId == "" {
		fl, err := c.GetResourceByName(TemplateDir, defaultDir.Id)
		if err != nil {
			return "", "", err
		}

		if fl == nil {
			templateDir, err = c.PutResources(TemplateDir, defaultDir.Id)
			if err != nil {
				return "", "", err
			}
		} else {
			templateDir = fl[0]
		}
	} else {
		fl, err := c.GetResources(templateId)
		if err != nil {
			return "", "", err
		}

		if fl == nil {
			templateDir, err = c.PutResources(TemplateDir, defaultDir.Id)
			if err != nil {
				return "", "", err
			}
		} else {
			templateDir = fl[0]
		}
	}

	// Check Save Directory
	if saveId == "" {
		fl, err := c.GetResourceByName(SaveDir, defaultDir.Id)
		if err != nil {
			return "", "", err
		}

		if fl == nil {
			saveDir, err = c.PutResources(SaveDir, defaultDir.Id)
			if err != nil {
				return "", "", err
			}
		} else {
			saveDir = fl[0]
		}
	} else {
		fl, err := c.GetResources(saveId)
		if err != nil {
			return "", "", err
		}

		if fl == nil {
			saveDir, err = c.PutResources(SaveDir, defaultDir.Id)
			if err != nil {
				return "", "", err
			}
		} else {
			saveDir = fl[0]
		}
	}

	return templateDir.Id, saveDir.Id, nil
}
