package handlers

import (
	"errors"
	"fmt"
	"github.com/Pena-Co-Ltd/amocrm_templategen_back/amo"
	"github.com/Pena-Co-Ltd/amocrm_templategen_back/dal/model"
	"github.com/Pena-Co-Ltd/amocrm_templategen_back/templategen"
	"github.com/gorilla/schema"
	"golang.org/x/oauth2"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

type ReqAmoSaveToken struct {
	AccessToken  string `json:"access_token" schema:"access_token"`
	Code         string `json:"code" schema:"code"`
	ClientID     string `json:"client_id" schema:"client_id"`
	ExpiresIn    int64  `json:"expires_in" schema:"expires_in"`
	TokenType    string `json:"token_type" schema:"token_type"`
	RefreshToken string `json:"refresh_token" schema:"refresh_token"`
	State        string `json:"state" schema:"state"`
	FromWidget   string `json:"from_widget" schema:"from_widget"`
	Referer      string `json:"referer" schema:"referer"`
}

func (h *Handlers) AmoSaveToken(w http.ResponseWriter, r *http.Request) {
	var req ReqAmoSaveToken

	err := r.ParseForm()
	if err != nil {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	err = schema.NewDecoder().Decode(&req, r.Form)
	if err != nil {
		h.reportError(w, err, 500)
		return
	}

	if req.AccessToken == "" && req.Code == "" {
		err = errors.New("AmoErr: got empty token")
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	token := &oauth2.Token{
		AccessToken:  req.AccessToken,
		RefreshToken: req.RefreshToken,
		TokenType:    req.TokenType,
		Expiry:       time.Now().Add(time.Duration(req.ExpiresIn) * time.Second),
	}

	amoClient, err := h.Amo.NewClient(r.Context(), req.Referer, token, req.Code)

	if err != nil {
		h.reportError(w, err, http.StatusForbidden)
		return
	}

	token = amoClient.Token

	user := getJwtUser(r)
	if user == nil {
		h.reportError(w, ErrorUnauthorized, http.StatusUnauthorized)
		return
	}

	amoAcc, err := amoClient.GetAccount()

	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
		return
	}

	// Insert/Update token in DB
	_, err = h.dal.Amo.InsertOrUpdate(r.Context(), &model.Amo{
		UserID:       user.UserID,
		AccountID:    strconv.FormatInt(amoAcc.Id, 10),
		AccessToken:  token.AccessToken,
		RefreshToken: token.RefreshToken,
		FromWidget:   req.FromWidget,
		Referer:      req.Referer,
		Subdomain:    amoAcc.Subdomain,
		ExpiresIn:    token.Expiry,
		TokenType:    token.TokenType,
	})

	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
		return
	}

	err = sendResponse(w, http.StatusOK, map[string]string{"accessToken": req.AccessToken})
	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
	}
}

func (h *Handlers) AmoWebhook(w http.ResponseWriter, r *http.Request) {
	reqBody, err := ioutil.ReadAll(r.Body)

	if err != nil {
		h.reportError(w, err, 200)
		return
	}

	p, err := url.ParseQuery(string(reqBody))
	if err != nil {
		h.reportError(w, err, 200)
		return
	}

	leadId := p.Get("leads[status][0][id]")
	subdomain := p.Get("account[subdomain]")
	accId := p.Get("account[id]")

	// Запрашиваем данные по аккаунту
	amoData, err := h.dal.Amo.GetByAccountID(r.Context(), accId)
	if err != nil {
		h.reportError(w, err, 200)
		return
	}

	amoClient, err := h.Amo.NewClient(r.Context(), subdomain, amoData.Token(), "")

	if err != nil {
		h.reportError(w, err, http.StatusForbidden)
		return
	}

	lead, err := amoClient.GetLeadById(leadId)
	if err != nil {
		h.reportError(w, err, 200)
		return
	}

	if lead != nil {
		// fmt.Printf("Lead RESP:%+v\r\n", lead)
	} else {
		fmt.Println("Lead empty")
	}

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

	// Добавляем Инфо Лида
	for k, v := range templategen.AmoLeadFieldsToRuMap(lead) {
		dataTemplate[k] = v
	}

	//filename := fmt.Sprintf("%v", dataTemplate["Filename"])

	// Добавялем инфо контактов
	contacts := []amo.Contact{}
	for _, data := range lead.Embedded.Contacts {
		contact, err := amoClient.GetContactById(strconv.Itoa(data.Id))
		if err == nil {
			contacts = append(contacts, *contact)
		} else {
			fmt.Println("Something Wrong1:", err)
		}
	}

	dataTemplate["Контакты"] = templategen.AmoContactsFieldsToRuMap(contacts)

	// Добавляем инфо компаний
	companies := []amo.Company{}
	for _, data := range lead.Embedded.Companies {
		company, err := amoClient.GetCompanyById(strconv.Itoa(data.Id))
		if err == nil {
			companies = append(companies, *company)
		} else {
			fmt.Println("Something Wrong2:", err)
		}
	}

	dataTemplate["Компании"] = templategen.AmoCompaniesFieldsToRuMap(companies)

	template, err := h.dal.Template.GetByLeadId(r.Context(), leadId)
	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
		return
	}

	if template == nil {
		h.reportError(w, err, http.StatusNotFound)
		return
	}

	switch template.Storage {
	case "gdisk":
		gdiskData, err := h.dal.GDisk.GetByID(r.Context(), template.StorageID)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		if gdiskData == nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		client, err := h.GDisk.NewClient(r.Context(), gdiskData.Token())

		err = templategen.GDiskGenerateDoc(template, gdiskData.SaveFolderID, client, dataTemplate)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}
	case "yadisk":
		yaDiskData, err := h.dal.YaDisk.GetByID(r.Context(), template.StorageID)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		client, err := h.YaDisk.NewClient(r.Context(), yaDiskData.Token(), "")

		err = templategen.YaDiskGenerateDoc(template, yaDiskData.SaveFolder, client, dataTemplate)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}
	default:
		h.reportError(w, errors.New("got unknown storage"), http.StatusInternalServerError)
		return
	}

	//fmt.Println("RESPONSE:", r.PostForm.Encode())
	sendResponse(w, 200, nil)
}

type RespAmoState struct {
	Visibility []int64 `json:"visibility"`
	Creation   []int64 `json:"creation"`
	Delete     []int64 `json:"delete"`
}

func (h *Handlers) AmoState(w http.ResponseWriter, r *http.Request) {
	amoData := getAmoByJwt(r)

	if amoData == nil {
		h.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
		return
	}

	resp := RespAmoState{
		Visibility: amoData.AccessRules.Visibility,
		Creation:   amoData.AccessRules.Creation,
		Delete:     amoData.AccessRules.Delete,
	}

	sendResponse(w, 200, resp)
}

type ReqAmoGetTemplate struct {
	LeadId     int64  `json:"lead_id"`
	TemplateId string `json:"template_id"`
}

// AmoGetTemplate - получить шаблон по LeadID или template.ID
func (h *Handlers) AmoGetTemplate(w http.ResponseWriter, r *http.Request) {
	var req ReqAmoGetTemplate

	amoData := getAmoByJwt(r)

	if amoData == nil {
		h.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
		return
	}

	err := decodePost(&req, r)
	if err != nil {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	if req.TemplateId == "" && strconv.FormatInt(req.LeadId, 10) == "" {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	var template *model.Template

	if req.TemplateId != "" {
		template, err = h.dal.Template.GetByID(r.Context(), req.TemplateId)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}
	}

	if strconv.FormatInt(req.LeadId, 10) != "" {
		template, err = h.dal.Template.GetByLeadId(r.Context(), strconv.FormatInt(req.LeadId, 10))
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}
	}

	if template == nil {
		templateId, err := h.dal.Template.Insert(r.Context(), &model.Template{
			UserID: amoData.UserID,
			LeadId: strconv.FormatInt(req.LeadId, 10),
		})
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		template = &model.Template{ID: templateId}

	}

	sendResponse(w, 200, template)
}

type ReqAmoSetTemplate struct {
	LeadId     int64  `json:"lead_id"`     // Required for insert/update
	TemplateId string `json:"template_id"` // Required for update - может потом удалить ?
	Filename   string `json:"filename"`    // Required for Yandex Disk
	FileID     string `json:"file_id"`     // Required for Google Drive
	StorageID  string `json:"storage_id"`  // Required. id gdisk or yadisk
	Storage    string `json:"storage"`     // Required. yadisk, gdisk - Может удалить потом?
}

type RespAmoSetTemplate struct {
	TemplateId string `json:"template_id"`
}

// AmoSetTemplate - устанавливает/обновляет шаблон для сделки
func (h *Handlers) AmoSetTemplate(w http.ResponseWriter, r *http.Request) {
	var req ReqAmoSetTemplate

	amoData := getAmoByJwt(r)

	if amoData == nil {
		h.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
		return
	}

	err := decodePost(&req, r)
	if err != nil {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	if strconv.FormatInt(req.LeadId, 10) == "" || req.Storage == "" || req.StorageID == "" {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	if req.Filename == "" && req.FileID == "" {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	// Search/update template

	var template *model.Template

	update := &model.Template{
		ID:        req.TemplateId,
		UserID:    amoData.UserID,
		LeadId:    strconv.FormatInt(req.LeadId, 10),
		Name:      "Sample",
		Filename:  req.Filename,
		StorageID: req.StorageID,
		FileID:    req.FileID,
		IsDeleted: false,
	}

	templateId := ""
	if req.TemplateId == "" {
		template, err = h.dal.Template.GetByLeadId(r.Context(), strconv.FormatInt(req.LeadId, 10))
		fmt.Println(0, template)

		if template != nil {
			err = h.dal.Template.UpdateByLeadID(r.Context(), update)
		} else {
			templateId, err = h.dal.Template.Insert(r.Context(), update)
		}
	} else {
		err = h.dal.Template.UpdateByID(r.Context(), update)
	}

	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
		return
	}

	sendResponse(w, 200, RespAmoSetTemplate{templateId})
}

type ReqAmoGenerateDoc struct {
	LeadId int64 `json:"lead_id"`
}

func (h *Handlers) AmoGenerateDoc(w http.ResponseWriter, r *http.Request) {
	var req ReqAmoGenerateDoc

	amoData := getAmoByJwt(r)

	if amoData == nil {
		h.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
		return
	}

	err := decodePost(&req, r)
	if err != nil {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	leadId := strconv.FormatInt(req.LeadId, 10)

	if leadId == "" {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	template, err := h.dal.Template.GetByLeadId(r.Context(), leadId)
	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
		return
	}

	if template == nil {
		h.reportError(w, err, http.StatusNotFound)
		return
	}

	// Получить данные по лиду
	amoClient, err := h.Amo.NewClient(r.Context(), amoData.Referer, amoData.Token(), "")

	if err != nil {
		h.reportError(w, err, http.StatusForbidden)
		return
	}

	lead, err := amoClient.GetLeadById(leadId)
	if err != nil {
		h.reportError(w, err, 200)
		return
	}

	if lead.Id == 0 {
		fmt.Println("Lead empty")
	}

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

	// Добавляем Инфо Лида
	for k, v := range templategen.AmoLeadFieldsToRuMap(lead) {
		dataTemplate[k] = v
	}

	//filename := fmt.Sprintf("%v_%v_%v.docx", req.Filename, amoData.UserID, time.Now().Unix())

	// Добавялем инфо контактов
	contacts := []amo.Contact{}
	for _, data := range lead.Embedded.Contacts {
		contact, err := amoClient.GetContactById(strconv.Itoa(data.Id))
		if err == nil {
			contacts = append(contacts, *contact)
		} else {
			fmt.Println("Something Wrong1:", err)
		}
	}

	dataTemplate["Контакты"] = templategen.AmoContactsFieldsToRuMap(contacts)

	// Добавляем инфо компаний
	companies := []amo.Company{}
	for _, data := range lead.Embedded.Companies {
		company, err := amoClient.GetCompanyById(strconv.Itoa(data.Id))
		if err == nil {
			companies = append(companies, *company)
		} else {
			fmt.Println("Something Wrong2:", err)
		}
	}

	dataTemplate["Компании"] = templategen.AmoCompaniesFieldsToRuMap(companies)

	switch template.Storage {
	case "gdisk":
		gdiskData, err := h.dal.GDisk.GetByID(r.Context(), template.StorageID)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		if gdiskData == nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		client, err := h.GDisk.NewClient(r.Context(), gdiskData.Token())

		err = templategen.GDiskGenerateDoc(template, gdiskData.SaveFolderID, client, dataTemplate)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}
	case "yadisk":
		yaDiskData, err := h.dal.YaDisk.GetByID(r.Context(), template.StorageID)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}

		client, err := h.YaDisk.NewClient(r.Context(), yaDiskData.Token(), "")

		err = templategen.YaDiskGenerateDoc(template, yaDiskData.SaveFolder, client, dataTemplate)
		if err != nil {
			h.reportError(w, err, http.StatusInternalServerError)
			return
		}
	default:
		h.reportError(w, errors.New("got unknown storage"), http.StatusInternalServerError)
		return
	}

	sendResponse(w, http.StatusOK, nil)
}

type ReqAmoAccessRules struct {
	Visibility []int64 `json:"visibility"`
	Creation   []int64 `json:"creation"`
	Delete     []int64 `json:"delete"`
}

func (h *Handlers) AmoAccessRules(w http.ResponseWriter, r *http.Request) {
	var req ReqAmoAccessRules

	amoData := getAmoByJwt(r)

	if amoData == nil {
		h.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized)
		return
	}

	isAdmin := r.Context().Value("amoIsAdmin").(bool)

	if !isAdmin {
		h.reportError(w, errors.New("need admin access"), http.StatusUnauthorized)
		return
	}

	err := decodePost(&req, r)
	if err != nil {
		h.reportError(w, err, http.StatusBadRequest)
		return
	}

	if (req.Visibility == nil || len(req.Visibility) == 0) &&
		(req.Creation == nil || len(req.Creation) == 0) &&
		(req.Delete == nil || len(req.Delete) == 0) {
		h.reportError(w, errors.New("empty request"), http.StatusBadRequest)
		return
	}

	err = h.dal.Amo.UpdateAccessRules(r.Context(), amoData.ID, &model.AmoAccessRules{
		Visibility: req.Visibility,
		Creation:   req.Creation,
		Delete:     req.Delete,
	})

	if err != nil {
		h.reportError(w, err, http.StatusInternalServerError)
		return
	}

	w.WriteHeader(200)
}
