package authDB

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"log"
	"sync"

	"net"
	"strconv"
	"time"

	"git.slaventius.ru/test3k/authDB/internal/config"
	repo "git.slaventius.ru/test3k/authDB/internal/customer"
	arango "git.slaventius.ru/test3k/authDB/internal/transport/arango"
	kafka "git.slaventius.ru/test3k/authDB/internal/transport/kafka"
	api "git.slaventius.ru/test3k/umate/pkg/api"
	apiKafka "git.slaventius.ru/test3k/umate/pkg/kafka"
	logger "git.slaventius.ru/test3k/umate/pkg/logger"
	// health "google.golang.org/grpc/health/grpc_health_v1"
)

type AuthDBServer struct {
	mu          sync.Mutex
	ctx         context.Context
	kafkaWriter *kafka.KafkaWriter
	logger      *logger.Logger
	repo        *repo.CustomerRepository
	api.UnimplementedAuthDBServer
	api.UnimplementedHealthServer
}

func NewServer(ctx context.Context, config *config.Config) *AuthDBServer {
	conn := arango.NewConnection(ctx, config.Arango.Host, config.Arango.Port)
	client := arango.NewClient(ctx, conn, config.Arango.User, config.Arango.Password)
	arangoDB := arango.NewDataBase(ctx, client, "test3k")
	logger := logger.NewLogger("test3k:authDBService", config.Sentry.DSN)
	repo := repo.NewCustomerRepository(ctx, arangoDB)

	//
	err := repo.FetchAll()
	if err != nil {
		log.Fatal(err)
	}

	return &AuthDBServer{
		mu:          sync.Mutex{},
		ctx:         ctx,
		repo:        repo,
		kafkaWriter: kafka.NewWriter(ctx, logger, apiKafka.TopicRegistrations, net.JoinHostPort(config.Kafka.Host, strconv.Itoa(config.Kafka.Port))),
		logger:      logger,
	}
}

func (r *AuthDBServer) getMD5Hash(text string) string {
	hash := md5.Sum([]byte(text))

	return hex.EncodeToString(hash[:])
}

func (s *AuthDBServer) GracefulStop() error {
	return s.kafkaWriter.Close()
}

func (s *AuthDBServer) Login(ctx context.Context, req *api.LoginRequest) (*api.LoginResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	//
	user, ok := s.repo.Customers[req.GetLogin()]
	if !ok {
		return nil, errors.New("login unknown")
	}

	//
	if !user.Confirmed {
		return nil, errors.New("login unconfirmed")
	}

	//
	if user.Password != s.getMD5Hash(req.Password) {
		return nil, errors.New("password incorrect")
	}

	return &api.LoginResponse{
		ID: 0,
	}, nil
}

func (s *AuthDBServer) Registration(ctx context.Context, req *api.RegistrationRequest) (*api.RegistrationResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	//
	user, ok := s.repo.Customers[req.GetLogin()]
	if !ok {
		hash := s.getMD5Hash(req.GetPassword())
		tmpUser, eru := s.repo.NewCustomer(req.GetLogin(), hash, req.GetEmail(), strconv.Itoa(time.Now().Nanosecond()))
		if eru != nil {
			s.logger.Error(eru)

			return nil, eru
		}

		user = tmpUser
	} else if user.Confirmed || time.Now().Before(user.Time) {
		return nil, errors.New("login already registered")
	} else { // Обновим время регистрации
		user.Refresh()
	}

	// TODO
	_, era := json.Marshal(user.MessageRegistration)
	if era != nil {
		return nil, era
	}

	//
	s.logger.Printf("publication code %s to %s ...", user.MessageRegistration.Code, user.MessageRegistration.Email)

	// //
	// err := s.kafkaWriter.WriteMessage([]byte(user.Login), value)
	// if err != nil {
	// 	s.logger.Error(err)

	// 	return nil, err
	// } else {
	s.repo.Customers[req.Login] = user
	// }

	//
	s.logger.Printf("publication code %s to %s completed", user.MessageRegistration.Code, user.MessageRegistration.Email)

	return &api.RegistrationResponse{
		Code:  user.MessageRegistration.Code,
		Email: user.MessageRegistration.Email,
	}, nil
}

func (s *AuthDBServer) Confirmation(ctx context.Context, req *api.ConfirmationRequest) (*api.ConfirmationResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	//
	for _, x := range s.repo.Customers {
		if x.Code == req.GetCode() {
			if x.Confirmed {
				return nil, errors.New("already confirmed")
			}

			//
			err := x.Confirm()
			if err != nil {
				return nil, err
			} else { // Что бы не перечитывать из БД, обновим локальную копию
				s.repo.Customers[x.Login] = x
			}

			return &api.ConfirmationResponse{
				ID: 0,
			}, nil
		}
	}

	return nil, errors.New("code unknown")
}

// func (s *AuthDBServer) Check(ctx context.Context, req *health.HealthCheckRequest) (*health.HealthCheckResponse, error) {
// 	s.logger.Println("🏥 K8s is health checking")
// 	s.logger.Printf("✅ Server's status is %s", api.HealthCheckResponse_SERVING)

// 	// if isDatabaseReady == true {
// 	// 	s.logger.Printf("✅ Server's status is %s", api.HealthCheckResponse_SERVING)

// 	// 	return &api.HealthCheckResponse{
// 	// 		Status: api.HealthCheckResponse_SERVING,
// 	// 	}, nil
// 	// } else if isDatabaseReady == false {
// 	// 	s.logger.Printf("🚫 Server's status is %s", api.HealthCheckResponse_NOT_SERVING)

// 	// 	return &api.HealthCheckResponse{
// 	// 		Status: api.HealthCheckResponse_NOT_SERVING,
// 	// 	}, nil
// 	// } else {
// 	// 	s.logger.Printf("🚫 Server's status is %s", api.HealthCheckResponse_UNKNOWN)

// 	// 	return &api.HealthCheckResponse{
// 	// 		Status: api.HealthCheckResponse_UNKNOWN,
// 	// 	}, nil
// 	// }

// 	return &health.HealthCheckResponse{
// 		Status: health.HealthCheckResponse_SERVING,
// 	}, nil
// }

// func (s *AuthDBServer) Watch(req *health.HealthCheckRequest, srv health.Health_WatchServer) error {
// 	return nil
// }