package service

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"heruvym/dal/minio"
	"heruvym/dal/mongo"
	"heruvym/jwt_adapter"
	"heruvym/model"
	"heruvym/tools"
	"net/http"
	"strings"
	"sync"

	"github.com/rs/xid"
	"github.com/themakers/hlog"
	tb "gopkg.in/tucnak/telebot.v2"
)

type Heruvym struct {
	logger hlog.Logger
	dal    *mongo.DAL
	bs     *minio.BlobStore

	notifier *tb.Bot
}

func New(
	blobs *minio.BlobStore,
	dataAccessLayer *mongo.DAL,
	log hlog.Logger,
	notifier *tb.Bot) *Heruvym {
	return &Heruvym{
		logger:   log.Module("Service"),
		dal:      dataAccessLayer,
		bs:       blobs,
		notifier: notifier,
	}
}

func (h Heruvym) Register(m *http.ServeMux) *http.ServeMux {

	m.HandleFunc("/create", h.CreateTicket)
	m.HandleFunc("/subscribe", tools.SseWrapper(h.GetList))
	m.HandleFunc("/ticket", tools.SseWrapper(h.Subscribe))
	m.HandleFunc("/send", tools.HandlerWrapper(h.PutMessage))
	m.HandleFunc("/getTickets", tools.HandlerWrapper(h.GetTickets))
	m.HandleFunc("/getMessages", tools.HandlerWrapper(h.GetMessages))
	m.HandleFunc("/pick", tools.HandlerWrapper(h.Pick))
	m.HandleFunc("/delegate", tools.HandlerWrapper(h.Delegate))
	m.HandleFunc("/vote", tools.HandlerWrapper(h.Vote))
	m.HandleFunc("/close", tools.HandlerWrapper(h.CloseTicket))

	return m
}

type CreateTicketReq struct {
	Title   string `json:"Title"`
	Message string `json:"Message"`
}

type CreateTicketResp struct {
	Ticket string `json:"Ticket"`
	Sess string `json:"sess"`
}

func (h *Heruvym) CreateTicket(w http.ResponseWriter, r *http.Request) {
	defer func() {
		if err := r.Body.Close(); err != nil {
			h.logger.Emit(ErrorClose{
				Err: err,
			})
		}
	}()

	var (
		err     error
		request CreateTicketReq
	)

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, "Invalid json", http.StatusBadRequest)
		return
	}

	if request.Title == "" {
		http.Error(w, "No Title", http.StatusNoContent)
		return
	}

	if request.Message == "" {
		http.Error(w, "No Message", http.StatusNoContent)
		return
	}

	ctx := r.Context()

	session := jwt_adapter.Get(ctx)

	if session == nil {
		http.Error(w, "No session", http.StatusMethodNotAllowed)
		return
	}

	var (
		ticketID string
		tickets  []model.Ticket
		role     = jwt_adapter.GetRole(ctx)
	)
	if role == "" {
		tickets, _, err = h.dal.GetTickets4Sess(ctx, session.Id)
	}

	if err != nil || len(tickets) == 0 {

		ticketID, err = h.dal.CreateTicket(
			ctx,
			session.Id,
			session.Id,
			request.Title,
			request.Message,
			[]string{},
		)
		if err != nil {
			http.Error(w, "CannotCreateTicket", http.StatusInternalServerError)
			return
		}

		if _, err := h.dal.PutMessage(ctx,
			request.Message,
			session.Id,
			session.Id,
			ticketID,
			[]string{},
		); err != nil {
			http.Error(w, "CannotCreateMessage", http.StatusInternalServerError)
			return
		}
	} else {
		ticketID = tickets[0].ID
	}

	response, err := json.Marshal(CreateTicketResp{Ticket: ticketID,Sess: session.Id})
	if err != nil {
		h.logger.Emit(ErrorMarshal{
			Err: err,
		})
		http.Error(w, "CannotMarshalMessage", http.StatusInternalServerError)
		return
	}

	if _, err := w.Write(response); err != nil {
		h.logger.Emit(ErrorMarshal{
			Err: err,
		})
		return
	}
}

var _ tools.DataEmitter = (&Heruvym{}).GetList

func (h *Heruvym) GetList(ctx context.Context) chan interface{} {
	defer func() {
		if rec := recover(); rec != nil {
			fmt.Println(rec)
		}
	}()
	sess := jwt_adapter.Get(ctx)

	if sess == nil {
		return nil
	}

	output := make(chan interface{})

	if sess.Id == "" {
		go h.unauthorizedTickets(ctx, sess.Id, output)
	} else {
		role := jwt_adapter.GetRole(ctx)

		fmt.Println("ALL TICKETS Sess ", sess.Id, role)
		if role == "admin" || role == "manager" {
			go h.allTickets(ctx, output)
		} else {
			go h.userTickets(ctx, sess.Id, output)
		}
	}

	return output
}

func (h *Heruvym) allTickets(ctx context.Context, output chan interface{}) {
	defer func() {
		if v := recover(); v != nil {
			fmt.Println("AllTicketsRec", v)
		}
	}()

	//data, count, err := h.dal.YieldTickets(ctx, 20)
	//
	//if err != nil {
	//	output <- errors.New("cannot get tickets:" + err.Error())
	//	return
	//}
	//
	//if data != nil {
	//	output <- GetTicketsResp{data, count}
	//}

	if err := h.dal.WatchAllTickets(ctx, func(ticket model.Ticket) error {
		output <- ticket
		return nil
	}); err != nil {
		output <- errors.New("cannot watch all tickets" + err.Error())
	}
}

func (h *Heruvym) userTickets(ctx context.Context, userID string, output chan interface{}) {

	defer func() {
		if v := recover(); v != nil {
			fmt.Println("USERTICKS", v)
		}
	}()

	data, count, err := h.dal.YieldUserTickets(ctx, userID, 20, 0)

	if err != nil {
		output <- errors.New("cannot get tickets:" + err.Error())
		return
	}

	if data != nil {
		output <- GetTicketsResp{data, count}
	}

	if err := h.dal.WatchTickets(ctx, userID, func(ticket model.Ticket) error {
		output <- ticket
		return nil
	}); err != nil {
		output <- errors.New("cannot watch tickets")
		return
	}
}

func (h *Heruvym) hasNoRole(output chan interface{}) {
	output <- errors.New("no role in profile")
}

func (h *Heruvym) unauthorizedTickets(ctx context.Context, sess string, output chan interface{}) {
	//defer close(output)

	tickets, count, err := h.dal.GetTickets4Sess(ctx, sess)

	if err != nil {
		output <- errors.New("no tickets for session")
		return
	}

	if tickets != nil {
		output <- GetTicketsResp{tickets, count}
	}
}

type ReqPutMessage struct {
	Message  string   `json:"message"`
	TicketID string   `json:"ticket"`
	Files    []string `json:"files"`
	Lang     string   `json:"lang"`
}

func (h *Heruvym) PutMessage(
	ctx context.Context,
	request ReqPutMessage,
) (interface{}, int) {
	sess := jwt_adapter.Get(ctx)

	request.Files = []string{}

	message, err := h.dal.PutMessage(
		ctx,
		request.Message,
		sess.Id,
		sess.Id,
		request.TicketID,
		[]string{},
	)
	if err != nil {
		return errors.New("can not put message"), http.StatusInternalServerError
	}

	go func() {
		if sess.Id != "" {
			additional, err := h.dal.GetAdditionalData(context.TODO(), sess.Id)
			fmt.Println("CAN NOT NOTIFY", err)

			if err == nil && h.notifier != nil {
				if _, err := h.notifier.Send(tb.ChatID(-1001344671794),
					fmt.Sprintf("Поступило новое сообщение от пользователя %d с почтой %s",
						additional.Uid, additional.Email)); err != nil {
					fmt.Println("CAN NOT NOTIFY", err)
				}
				return
			}
		}
		if h.notifier != nil {
			if _, err := h.notifier.Send(tb.ChatID(-1001344671794),
				fmt.Sprintf(
					"Поступило новое сообщение от незарегистриованного пользователя")); err != nil {
				fmt.Println("CAN NOT NOTIFY", err)
			}
		}
	}()

	if err := h.dal.UpdateTopMessage(ctx, request.TicketID, message); err != nil {
		return errors.New("can not update ticket"), http.StatusInternalServerError
	}

	return nil, http.StatusOK
}

type ReqScreenshot struct {
	TicketID string `json:"ticket"`
	Lang     string `json:"lang"`
}

func (h *Heruvym) RequestScreenshot(
	ctx context.Context,
	request ReqScreenshot,
) (interface{}, int) {
	sess := jwt_adapter.Get(ctx)

	_, err := h.dal.PutSCRequest(
		ctx,
		sess.Id,
		sess.Id,
		request.TicketID,
	)
	if err != nil {
		return errors.New("can not put message"), http.StatusInternalServerError
	}

	return nil, http.StatusOK
}

var _ tools.DataEmitter = (&Heruvym{}).Subscribe

func (h *Heruvym) Subscribe(ctx context.Context) chan interface{} {
	sess := jwt_adapter.Get(ctx)
	fmt.Println("SESS Subsc", sess)

	ticketID := ctx.Value(tools.ContextURLKey).(string)

	output := make(chan interface{})
	if sess.Id == "" {
		go func() {
			ticket, err := h.dal.GetTicket4Sess(ctx, ticketID, sess.Id)
			if err != nil || ticket == nil {
				output <- errors.New("no tickets 4 session")
				return
			}

			if err := h.dal.YieldMessages(ctx, ticketID, func(message model.Message) error {
				output <- message
				fmt.Println("OOOOOOLd")

				//if err := h.dal.SetShown(ctx, message.ID, sess.Session); err != nil {
				//
				//	output <- errors.New("cannot show message " + err.Error())
				//	return err
				//}

				return nil
			}); err != nil {
				output <- errors.New("cannot read messages " + err.Error())
			}

			if err := h.dal.WatchMessages(ctx, ticketID,
				func(message model.Message) error {
					output <- message

					//if err := h.dal.SetShown(ctx, message.ID, sess.Session); err != nil {
					//	fmt.Println("3", err)
					//	output <- errors.New("cannot show watch message " + err.Error())
					//	return err
					//}

					return nil
				}); err != nil {
				fmt.Println("4", err)
				output <- errors.New("cannot watch messages " + err.Error())
			}
		}()
	} else {
		role := jwt_adapter.GetRole(ctx)

		if role == "admin" || role == "manager" {
			go func() {

				if err := h.dal.YieldMessages(ctx, ticketID, func(message model.Message) error {
					output <- message

					//if err := h.dal.SetShown(ctx, message.ID, sess.User); err != nil {
					//	fmt.Println("2", err)
					//	output <- errors.New("cannot show message " + err.Error())
					//	return err
					//}

					return nil
				}); err != nil {
					fmt.Println("1", err)
					output <- errors.New("cannot read messages " + err.Error())
				}

				if err := h.dal.WatchMessages(ctx, ticketID,
					func(message model.Message) error {
						output <- message

						//if err := h.dal.SetShown(ctx, message.ID, sess.Session); err != nil {
						//	fmt.Println("3", err)
						//	output <- errors.New("cannot show watch message " + err.Error())
						//	return err
						//}

						return nil
					}); err != nil {
					fmt.Println("4", err)
					output <- errors.New("cannot watch messages " + err.Error())
				}
			}()
		} else {
			go func() {
				defer func() {
					if v := recover(); v != nil {
						fmt.Println("heryvym panic", v)
					}
				}()
				ticket, err := h.dal.GetTicket4User(ctx, ticketID, sess.Id)
				if err != nil || ticket == nil {
					output <- errors.New("no tickets 4 user")
				}

				if err := h.dal.YieldMessages(ctx, ticketID, func(message model.Message) error {
					output <- message

					//if err := h.dal.SetShown(ctx, message.ID, sess.User); err != nil {
					//	fmt.Println("2", err)
					//	output <- errors.New("cannot show message " + err.Error())
					//	return err
					//}

					return nil
				}); err != nil {
					fmt.Println("1", err)
					output <- errors.New("cannot read messages " + err.Error())
				}

				if err := h.dal.WatchMessages(ctx, ticketID,
					func(message model.Message) error {
						output <- message

						//if err := h.dal.SetShown(ctx, message.ID, sess.Session); err != nil {
						//	fmt.Println("3", err)
						//	output <- errors.New("cannot show watch message " + err.Error())
						//	return err
						//}

						return nil
					}); err != nil {
					fmt.Println("4", err)
					output <- errors.New("cannot watch messages " + err.Error())
				}
			}()
		}
	}

	return output
}

func (h *Heruvym) handleOwnMessages(output chan interface{}) {
	defer close(output)
}

type GetTicketsReq struct {
	Amount int64  `json:"amt"`
	Page   int64  `json:"page"`
	Search string `json:"srch"`
	Status string `json:"status"`
}
type GetTicketsResp struct {
	Data  []model.Ticket `json:"data"`
	Count int64          `json:"count"`
}

func (h *Heruvym) GetTickets(
	ctx context.Context,
	request GetTicketsReq) (GetTicketsResp, int) {
	
	role := jwt_adapter.GetRole(ctx)

	if role == "admin" {
		result, count, err := h.dal.GetTicketPage(ctx,
			request.Status,
			request.Search,
			request.Amount,
			request.Page,
		)
		if err != nil {
			return GetTicketsResp{}, http.StatusNoContent
		}

		return GetTicketsResp{
			Data:  *result,
			Count: count,
		}, http.StatusOK
	} else {

	sess := jwt_adapter.Get(ctx)

		result, count, err := h.dal.YieldUserTickets(ctx, sess.Id, request.Amount, request.Page*request.Amount)
		if err != nil {
			return GetTicketsResp{}, http.StatusNoContent
		}

		return GetTicketsResp{
			Data:  *result,
			Count: count,
		}, http.StatusOK
	}
}

type GetMessagesReq struct {
	Amount   int64  `json:"amt"`
	Page     int64  `json:"page"`
	Search   string `json:"srch"`
	TicketID string `json:"ticket"`
}

func (h *Heruvym) GetMessages(
	ctx context.Context,
	request GetMessagesReq) ([]model.Message, int) {
	result, err := h.dal.GetMessagesPage(ctx,
		request.Search,
		request.TicketID,
		request.Amount,
		request.Page,
	)
	if err != nil {
		return nil, http.StatusNoContent
	}

	return result, http.StatusOK
}

type CloseTicketReq struct {
	TicketID string `json:"ticket"`
}
type CloseTicketResp struct {
	TicketID string `json:"ticket"`
}

func (h *Heruvym) CloseTicket(ctx context.Context, req CloseTicketReq) (*CloseTicketResp, int) {
	if err := h.dal.SetTicketStatus(ctx, req.TicketID, model.StateClose); err != nil {
		return nil, http.StatusBadRequest
	}

	if _, err := h.dal.PutMessage(ctx, "close", "close", "close", req.TicketID, []string{}); err != nil {
		return nil, http.StatusBadRequest
	}

	return &CloseTicketResp{
		TicketID: req.TicketID,
	}, http.StatusOK
}

type VoteReq struct {
	TicketID string `json:"ticket"`
	Rate     int    `json:"rate"`
}

func (h *Heruvym) Vote(ctx context.Context, req VoteReq) (error, int) {
	if err := h.dal.SetRate(ctx, req.TicketID, req.Rate); err != nil {
		return err, http.StatusBadRequest
	}

	return nil, http.StatusOK
}

type PickReq struct {
	TicketID string `json:"ticket"`
}

func (h *Heruvym) Pick(ctx context.Context, req PickReq) (error, int) {
	sess := jwt_adapter.Get(ctx)
	if err := h.dal.SetAnswerer(ctx, req.TicketID, sess.Id); err != nil {
		return err, http.StatusBadRequest
	}

	return nil, http.StatusOK
}

type DelegateReq struct {
	TicketID   string `json:"ticket"`
	AnswererID string `json:"answerer"`
}

func (h *Heruvym) Delegate(ctx context.Context, req DelegateReq) (error, int) {
	if err := h.dal.SetAnswerer(ctx, req.TicketID, req.AnswererID); err != nil {
		return err, http.StatusBadRequest
	}

	return nil, http.StatusOK
}

// MB Size constants
const (
	MB = 1 << 20
)

type PutFileReq struct {
	Ticket string `json:"ticket"`
}

type PutFileResp struct {
	Message string `json:"message"`
}

func (h *Heruvym) PutFile(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	if err := r.ParseMultipartForm(10 * MB); err != nil {
		if _, err := w.Write([]byte("can not parse multipart " + err.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if r.MultipartForm == nil {
		if _, err := w.Write([]byte("no multipart")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if r.MultipartForm.File == nil {
		if _, err := w.Write([]byte("no file")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	filesCount := len(r.MultipartForm.File)

	if filesCount == 0 {
		if _, err := w.Write([]byte("no files")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	sess := jwt_adapter.Get(r.Context())
	if sess == nil {
		if _, err := w.Write([]byte("not authorized")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	var req PutFileReq
	req.Ticket = r.MultipartForm.Value["ticket"][0]

	var (
		fileIDs, filenames []string
		errFile            error
	)
	wg := new(sync.WaitGroup)
	m := new(sync.Mutex)

	wg.Add(filesCount)
	for name, file := range r.MultipartForm.File {
		file := file
		name := name
		go func() {
			defer wg.Done()

			freader, err := file[0].Open()
			if err != nil {
				fmt.Println("can not open ", err.Error())
			}

			defer func() {
				if err := freader.Close(); err != nil {
					errFile = err
				}
			}()

			splitted := strings.Split(name, ".")
			filename := fmt.Sprintf("%s.%s", xid.New().String(), splitted[len(splitted)-1])

			if err := h.bs.PutFile(
				r.Context(),
				filename,
				freader,
				file[0].Size); err != nil {
				errFile = err
			}

			m.Lock()
			defer m.Unlock()
			fileIDs = append(fileIDs, filename)
			filenames = append(filenames, name)
		}()
	}
	wg.Wait()

	if errFile != nil {
		if _, err := w.Write([]byte("can not store files " + errFile.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	message, err := h.dal.PutMessage(
		r.Context(),
		strings.Join(filenames, ", "),
		sess.Id,
		sess.Id,
		req.Ticket,
		fileIDs,
	)

	if err != nil {
		for _, filename := range filenames {
			if err := h.bs.DeleteFile(r.Context(), filename); err != nil {
				fmt.Println("can not delete", err)
			}
		}

		if _, err := w.Write([]byte("can not store message " + err.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	resp, err := json.Marshal(&PutFileResp{Message: message.ID})
	if err != nil {
		if _, err := w.Write([]byte("can not marshal resp " + err.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if _, err := w.Write(resp); err != nil {
		fmt.Println("CAN NOT WRITE", err)
	}
	w.WriteHeader(http.StatusOK)
}

type PutSCReq struct {
	Ticket string `json:"ticket"`
}

type PutSCResp struct {
	Message string `json:"message"`
}

func (h *Heruvym) PutSC(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	if err := r.ParseMultipartForm(10 * MB); err != nil {
		if _, err := w.Write([]byte("can not parse multipart " + err.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if r.MultipartForm == nil {
		if _, err := w.Write([]byte("no multipart")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if r.MultipartForm.File == nil {
		if _, err := w.Write([]byte("no file")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	filesCount := len(r.MultipartForm.File)

	if filesCount == 0 {
		if _, err := w.Write([]byte("no files")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	sess := jwt_adapter.Get(r.Context())
	if sess == nil {
		if _, err := w.Write([]byte("not authorized")); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	var req PutFileReq

	req.Ticket = r.MultipartForm.Value["ticket"][0]
	var (
		fileIDs, filenames []string
		errFile            error
	)
	wg := new(sync.WaitGroup)
	m := new(sync.Mutex)

	wg.Add(filesCount)
	for name, file := range r.MultipartForm.File {
		file := file
		name := name
		go func() {
			defer wg.Done()

			freader, err := file[0].Open()
			if err != nil {
				fmt.Println("can not open ", err.Error())
			}

			defer func() {
				if err := freader.Close(); err != nil {
					errFile = err
				}
			}()

			splitted := strings.Split(name, ".")
			filename := fmt.Sprintf("%s.%s", xid.New().String(), splitted[len(splitted)-1])

			if err := h.bs.PutFile(
				r.Context(),
				filename,
				freader,
				file[0].Size); err != nil {
				errFile = err
			}

			m.Lock()
			defer m.Unlock()
			fileIDs = append(fileIDs, filename)
			filenames = append(filenames, name)
		}()
	}
	wg.Wait()

	if errFile != nil {
		if _, err := w.Write([]byte("can not store files " + errFile.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	message, err := h.dal.PutSCResponse(
		r.Context(),
		strings.Join(filenames, ", "),
		sess.Id,
		sess.Id,
		req.Ticket,
		fileIDs,
	)

	if err != nil {
		for _, filename := range filenames {
			if err := h.bs.DeleteFile(r.Context(), filename); err != nil {
				fmt.Println("can not delete", err)
			}
		}

		if _, err := w.Write([]byte("can not store message " + err.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	resp, err := json.Marshal(&PutFileResp{Message: message.ID})
	if err != nil {
		if _, err := w.Write([]byte("can not marshal resp " + err.Error())); err != nil {
			fmt.Println("CAN NOT WRITE", err)
		}
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if _, err := w.Write(resp); err != nil {
		fmt.Println("CAN NOT WRITE", err)
	}
	w.WriteHeader(http.StatusOK)
}
