package services

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-mail/mail"
	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"golang.org/x/crypto/bcrypt"
	"linhdevtran99/rest-api/models"
	"linhdevtran99/rest-api/utils"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

// CheckAndValidRegisterFiled Check the user register field is valid or not
func CheckAndValidRegisterFiled(registerData *models.CreateUser, w http.ResponseWriter) (bool, *models.ResponseError) {
	_ = models.Validate.RegisterValidation("customPassword", models.PasswordValidator)
	errs := models.Validate.Struct(registerData)
	type errStack []string
	errorAPI := models.ErrorAPI{
		Errors:  make([]string, 0),
		Message: "Error in input data please check again",
		Type:    "InvalidInput",
	}
	if errs != nil {
		for _, err := range errs.(validator.ValidationErrors) {
			switch err.Field() {
			case "Email":
				{
					fmt.Println("Internal Log: Email không hợp lệ")
					errorAPI.Errors = append(errorAPI.Errors, "Email")
				}
			case "Username":
				{
					fmt.Println("Internal Log: User không hợp lệ")
					errorAPI.Errors = append(errorAPI.Errors, "User")
				}
			case "Password":
				{
					fmt.Println("Internal Log: Password không hợp lệ")
					errorAPI.Errors = append(errorAPI.Errors, "Password")
				}
			case "ConfirmPassword":
				{
					fmt.Println("Internal Log: Nhập lại mật khẩu sai")
					errorAPI.Errors = append(errorAPI.Errors, "ConfirmPassword")
				}
			case "PhoneNumber":
				{
					fmt.Println("Internal Log: SĐT không hợp lệ")
					errorAPI.Errors = append(errorAPI.Errors, "PhoneNumber")
				}
			}
		}
		//handle repose error
		return false, &models.ResponseError{Code: http.StatusBadRequest, Err: errorAPI}
	}

	return true, nil

}

// CheckAccountExist Checking in db is user input same data in add time to resend verify mail
func CheckAccountValid(userName string, email string) (bool, *models.ResponseError) {
	//filter in mongodb
	var isError bool
	var errAPI *models.ResponseError
	var dataRedisRetrive models.RedisOTP

	//create context handler with timeout
	timeout := 2 * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	filter := bson.D{
		{"$or", bson.A{
			bson.D{{"username", userName}},
			bson.D{{"email", email}},
		}},
	}

	cursor, err := utils.User.Find(ctx, filter)

	if err != nil {
		fmt.Println("Internal Log: Internal error")
		isError = true
		errAPI = &models.ResponseError{
			Code: http.StatusInternalServerError,
			Err: models.ErrorAPI{
				Errors:  []string{"Internal error"},
				Message: "Internal error please try again later",
				Type:    "InternalError",
			},
		}
		return isError, errAPI
	}

	if cursor.Next(ctx) {
		fmt.Println("Internal Log: Email or username already had register")
		isError = true
		errAPI = &models.ResponseError{
			Code: http.StatusBadRequest,
			Err: models.ErrorAPI{
				Errors:  []string{"Account existed"},
				Message: "Email or username already had register",
				Type:    "AccountExisted",
			},
		}
	}

	retrievedValue, err := utils.Redis.Get(ctx, "otp:"+email).Result()
	if err != nil {

		if strings.Contains(err.Error(), "dial tcp 10.10.0.217:6391: connect: connection refused") {
			fmt.Println("Internal Log: Can not connect to redis server")
			isError = true
			errAPI = &models.ResponseError{
				Code: http.StatusInternalServerError,
				Err: models.ErrorAPI{
					Errors:  []string{"Internal error"},
					Message: "Internal error please try again later",
					Type:    "InternalError",
				},
			}
			return isError, errAPI
		}

		if err.Error() == "redis: nil" {
			fmt.Println("Internal Log: Can't get data from redis or data not exist or first time register")
		}
	}
	err = json.Unmarshal([]byte(retrievedValue), &dataRedisRetrive)

	timeDiff := time.Now().Unix() - dataRedisRetrive.CreatedDate

	if timeDiff < 30 {
		isError = true
		fmt.Println("Internal Log: Rate limit send verify mail")
		errAPI = &models.ResponseError{
			Code: http.StatusBadRequest,
			Err: models.ErrorAPI{
				Errors:  []string{"Rate limit send verify mail"},
				Message: "wait " + strconv.FormatInt(30-timeDiff, 10) + " to send verify mail again",
				Type:    "RateLimit",
			},
		}

	}

	return isError, errAPI
}

// GeneratorOtp Generate OTP Verify Link and Qr Link

const (
	otpDigits = 6
)

// CreateLinkVerify Create a alternative verify link
func CreateMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, mailChan chan models.MailVefiry, w http.ResponseWriter, errChan chan error) {
	smtpPass := os.Getenv("SMTP_PASS")
	m := mail.NewMessage()

	opt := <-otpChan
	mailVerify := <-mailChan

	decodedImage, err := base64.StdEncoding.DecodeString(mailVerify.ImageBase64)
	if err != nil {
		log.Println("Error decoding base64 image:", err)
	}

	qrFileName := registerInfo.Email + registerInfo.Username + ".png"
	_ = os.WriteFile("./temp/"+qrFileName, decodedImage, 0666)
	emailBody := utils.BuildEmail(opt.PureOTP, mailVerify.LinkMail, qrFileName)

	m.SetHeader("From", "admin@wliafdew.dev")
	m.SetHeader("To", registerInfo.Email)
	m.SetHeader("Subject", "Thanks For Join My Business")
	m.SetBody("text/html", emailBody)
	m.Embed("./temp/" + qrFileName)

	d := mail.NewDialer("mail.wliafdew.dev", 465, "admin@wliafdew.dev", smtpPass)
	d.StartTLSPolicy = mail.MandatoryStartTLS

	// Send the email to Bob, Cora and Dan.
	if err := d.DialAndSend(m); err != nil {
		errChan <- err
		defer func() {
			_ = os.Remove("./temp/" + qrFileName)
		}()
		return
	}
	errChan <- nil

	defer func() {
		_ = os.Remove("./temp/" + qrFileName)
	}()
}

// check and write pre use into mongodb
func CheckAndWritePreuser(registerInfo *models.PreusersMongo, wg *sync.WaitGroup, ctxCancel context.CancelFunc, errorChan error) (uint64, time.Time) {
	//generate timestamp
	timeCreate := time.Now()
	registerInfo.CreatedDate = timeCreate
	//create context handler with timeout
	timeout := 2 * time.Second
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	//checking
	email := registerInfo.Email
	count := 1
	filter := bson.D{{"email", email}}

	raw, err := utils.PreUserData.FindOne(ctx, filter).Raw()

	if errors.Is(err, mongo.ErrClientDisconnected) {
		errorChan = errors.New("can't connect to mongodb")
		ctxCancel()
		wg.Done()
		fmt.Println("Internal Log: Can't connect to mongodb")
	}

	if errors.Is(err, mongo.ErrNoDocuments) {
		hashed, err := bcrypt.GenerateFromPassword([]byte(registerInfo.HashPassword), 10)
		if err != nil {
			errorChan = errors.New("fail to hash password")
			ctxCancel()
			wg.Done()
			fmt.Println("Internal Log :Fail to hash password")
		}
		registerInfo.HashPassword = string(hashed)
		_, err = utils.PreUserData.InsertOne(context.Background(), registerInfo)
		if err != nil {
			errorChan = errors.New("can't insert data to PreUser")
			ctxCancel()
			wg.Done()
			fmt.Println("Internal Log: Can't Insert Data To PreUser")
		}
		wg.Done()
		return uint64(count), timeCreate
	}

	update := bson.D{
		{"$inc", bson.D{
			{"verify_sent_count", 1},
		}},
		{"$set", bson.D{
			{"update_date", timeCreate},
		},
		}}

	_, err = utils.PreUserData.UpdateOne(context.Background(), filter, update)
	if err != nil {
		errorChan = errors.New("update document fail")
		ctxCancel()
		wg.Done()
		fmt.Println("Internal log: Update document fail")
	}
	count = int(raw.Lookup("verify_sent_count").Int32() + 1)
	wg.Done()
	return uint64(count), timeCreate

}

//write otp in redis for valid if otp expired

func WriteOTPInRedis(counter uint64, registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, w http.ResponseWriter) {

	otp := <-otpChan

	dataRedis := &models.RedisOTP{
		Email:       registerInfo.Email,
		User:        registerInfo.Username,
		CreatedDate: registerInfo.CreatedDate.Unix(),
		HashOTP:     otp.HashOTP,
		Counter:     counter,
	}

	jsonData, err := json.Marshal(dataRedis)
	if err != nil {
		fmt.Println("Internal log: Can't stringfy json data")
		_ = utils.WriteJSONInternalError(w, "Can't stringfy json data")
	}
	status := utils.Redis.Set(context.Background(), "otp:"+registerInfo.Email, string(jsonData), time.Hour*24)

	fmt.Println(status.Err())

}

// ////////////////////////Create User Complete//////////////////////////
func CreateUserAfterVerify(email string, w http.ResponseWriter) bool {
	isValid := true
	var preUserData models.PreusersMongo
	filter := bson.D{{"email", email}}
	err := utils.PreUserData.FindOne(context.Background(), filter).Decode(&preUserData)

	if err != nil {
		fmt.Println("Internal log: Can't get preuser data")
		//_ = utils.WriteJSONInternalError(w, "Please register again")
		isValid = false
	}

	user := models.UsersMongo{
		Username:        preUserData.Username,
		Email:           preUserData.Email,
		PhoneNumber:     preUserData.PhoneNumber,
		HashPassword:    preUserData.HashPassword,
		Active:          true,
		CreatedDate:     time.Now(),
		UpdateDate:      time.Now(),
		VerifySentCount: preUserData.VerifySentCount,
	}

	_, err = utils.User.InsertOne(context.Background(), user)

	if err != nil {
		fmt.Println("Internal log: Can't insert user data")
		//_ = utils.WriteJSONInternalError(w, "Can't insert user data")
		isValid = false
	}

	if isValid == true {
		fmt.Println("Internal log: Success verify account")
		_ = utils.WriteJSON(w, http.StatusOK, models.SuccessAPI{
			Message: "Success verify account",
			Type:    "SuccessVerify",
		})
	}

	return isValid

}

//////////////////////////Verify OTP//////////////////////////

func CheckUserVerify(email string, w http.ResponseWriter) bool {
	isVerified := false
	filter := bson.D{{"email", email}}
	var user models.UsersMongo
	_ = utils.User.FindOne(context.Background(), filter).Decode(&user)

	if user.Active == true {
		fmt.Println("Internal log: Account already verify")
		_ = utils.WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{
			Errors:  []string{"Account already verify"},
			Message: "Account already verify",
			Type:    "AccountAlreadyVerify",
		})
		isVerified = true
	}

	return isVerified

}

func CheckOTPIsValid(otpInfo *models.OTPVerify, w http.ResponseWriter) bool {
	var isValid bool
	isValid = true
	//get data from redis
	var dataRedis models.RedisOTP
	//verify uuid and email
	value, err := utils.Redis.Get(context.Background(), "otp:"+otpInfo.Email).Result()
	if err != nil {
		fmt.Println("Internal log: Email not valid")
		_ = utils.WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{
			Errors:  []string{"Email not valid"},
			Message: "Email not valid",
			Type:    "EmailNotValid",
		})
		isValid = false
		return false
	}

	_ = json.Unmarshal([]byte(value), &dataRedis)
	_, err = uuid.Parse(otpInfo.UUID)
	if err != nil {
		fmt.Println("Internal log: UUID not valid")
		_ = utils.WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{
			Errors:  []string{"UUID not valid"},
			Message: "UUID not valid",
			Type:    "UUIDNotValid",
		})
		isValid = false
	}

	if isVerified := CheckUserVerify(otpInfo.Email, w); isVerified == true {
		return false
	}

	isValid = utils.VerifyOTP(otpInfo, dataRedis, w)

	if isValid == true {
		CreateUserAfterVerify(otpInfo.Email, w)
	}
	return true
}

//////////////////////////Verify JWT//////////////////////////

func VerifyWithJWT(email string, w http.ResponseWriter) bool {
	var isValid bool
	isValid = true
	//verify uuid and email

	if isVerified := CheckUserVerify(email, w); isVerified == true {
		return false
	}

	if isValid == true {
		CreateUserAfterVerify(email, w)
	}
	return true
}
