package account_client

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"github.com/gofiber/fiber/v2"
	"go.uber.org/zap"
	"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
	qutils "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/controller/http"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
	"time"
)

type Deps struct {
	MiddleWare  *http.MiddleWare
	AccountRepo *repository.AccountRepository
	Logger      *zap.Logger
	Encrypt     *qutils.Encrypt
	AuthClient  *client.AuthClient
}

type AccountController struct {
	middleWare  *http.MiddleWare
	accountRepo *repository.AccountRepository
	logger      *zap.Logger
	encrypt     *qutils.Encrypt
	authClient  *client.AuthClient
}

func NewAccountController(deps Deps) *AccountController {
	return &AccountController{
		middleWare:  deps.MiddleWare,
		accountRepo: deps.AccountRepo,
		logger:      deps.Logger,
		encrypt:     deps.Encrypt,
		authClient:  deps.AuthClient,
	}
}

func (receiver *AccountController) Delete(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	account, err := receiver.accountRepo.Remove(ctx.Context(), userID)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	return ctx.Status(fiber.StatusOK).JSON(account)
}

func (receiver *AccountController) Update(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	var request models.Name
	if err := ctx.BodyParser(&request); err != nil {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind json", err)
	}

	account, err := receiver.accountRepo.UpdateName(ctx.Context(), userID, &request)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	return ctx.Status(fiber.StatusOK).JSON(account)
}

func (receiver *AccountController) SetVerificationStatus(ctx *fiber.Ctx) error {
	userID := ctx.Params("userId")
	if userID == "" {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "invalid format for parameter userId")
	}

	var request models.SetAccountStatus
	if err := ctx.BodyParser(&request); err != nil {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "failed to bind json", err)
	}

	account, err := receiver.accountRepo.SetStatus(ctx.Context(), userID, request.Status)
	if err != nil {
		receiver.logger.Error("failed to set status on <SetVerificationStatus> of <AccountService>", zap.Error(err))
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	return ctx.Status(fiber.StatusOK).JSON(account)
}

func (receiver *AccountController) Get(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	hlogger := log_mw.ExtractLogger(ctx)

	account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID)
	if err != nil {
		if err.Type() == errors.ErrNotFound {
			return receiver.middleWare.Error(ctx, fiber.StatusNotFound, "no account", err)
		}
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	hlogger.Emit(models.InfoGetAccount{
		CtxUserID:    userID,
		CtxAccountID: account.ID,
	})

	return ctx.Status(fiber.StatusOK).JSON(account)
}

func (receiver *AccountController) Create(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	hlogger := log_mw.ExtractLogger(ctx)

	var er error

	quizFrom := ctx.Cookies("quizFrom")
	quizUser := ctx.Cookies("quizUser")
	if quizUser != "" {
		quizUser, er = receiver.encrypt.DecryptStr([]byte(quizUser))
		if er != nil {
			return receiver.middleWare.ErrorOld(ctx, er)
		}
	}

	account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID)
	if err != nil && err.Type() != errors.ErrNotFound {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	if account != nil {
		return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "account exists")
	}

	user, err := receiver.authClient.GetUser(ctx.Context(), userID)
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	account, err = receiver.accountRepo.Insert(ctx.Context(), &models.Account{
		UserID: user.ID, Wallet: models.Wallet{Currency: models.DefaultCurrency}, From: quizFrom, Partner: quizUser})
	if err != nil {
		return receiver.middleWare.ErrorOld(ctx, err)
	}

	quiz := ""
	if quizFrom != "" {
		quiz = "quiz"
	}

	hlogger.Emit(models.InfoCreateAccount{
		CtxUserID:      userID,
		CtxAccountID:   account.ID,
		KeyFromSource:  quiz,
		KeyFromID:      quizFrom,
		KeyFromPartner: quizUser,
		CtxLogin:       user.Login,
		CtxEmail:       user.Email,
		CtxPhone:       user.PhoneNumber,
		KeyCurrency:    account.Wallet.Currency,
	})

	return ctx.Status(fiber.StatusOK).JSON(account)
}

func (receiver *AccountController) AccountPipe(ctx *fiber.Ctx) error {
	userID, ok := receiver.middleWare.ExtractUserID(ctx)
	if !ok || userID == "" {
		return receiver.middleWare.NoAuth(ctx)
	}

	ctx.Set(fiber.HeaderContentType, "text/event-stream")
	ctx.Set("Cache-Control", "no-cache")
	ctx.Set("Connection", "keep-alive")
	ctx.Set("Transfer-Encoding", "chunked")

	accountCh := make(chan map[string]interface{})
	cancelCtx, cancel := context.WithCancel(ctx.Context())

	go func(ctx context.Context) {
		defer close(accountCh)
		if err := receiver.accountRepo.AccountPipe(ctx, userID, accountCh); err != nil {
			receiver.logger.Error("error in account pipe repo method", zap.Error(err))
		}
	}(cancelCtx)

	ctx.Status(fiber.StatusOK).Context().SetBodyStreamWriter(func(w *bufio.Writer) {
		pingTicker := time.NewTicker(5 * time.Second)
		defer pingTicker.Stop()
		for {
			select {
			case accountData, ok := <-accountCh:
				if !ok {
					return
				}
				accountJSON, err := json.Marshal(accountData)
				if err != nil {
					//receiver.logger.Error("error marshal account JSON", zap.Error(err))
					continue
				}
				fmt.Fprintf(w, "data: %s\n\n", accountJSON)
				if err := w.Flush(); err != nil {
					receiver.logger.Error("error flushing", zap.Error(err))
					cancel()
					receiver.logger.Info("Close connection Account Pipe sse")
					return
				}
			case <-pingTicker.C:
				fmt.Fprintf(w, "data: %s\n\n", `{"event": "ping"}`)
				if err := w.Flush(); err != nil {
					receiver.logger.Error("error sending ping Account Pipe sse, close connection", zap.Error(err))
					cancel()
					return
				}
			}
		}
	})

	return nil
}
