package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/caarlos0/env/v8"
	"github.com/google/uuid"
	"github.com/gorilla/mux"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
)

type Config struct {
	Port string `env:"USER_HTTP_ADDR" envDefault:"13998"`

	PgPort   string `env:"PG_PORT" envDefault:"5432"`
	PgHost   string `env:"PG_HOST" envDefault:"bd2"`
	PgDBName string `env:"PG_DB_NAME" envDefault:"service2"`
	PgUser   string `env:"PG_USER" envDefault:"postgres"`
	PgPwd    string `env:"PG_PWD" envDefault:"159753"`
}

func main() {
	cfg := Config{}
	if err := env.Parse(&cfg); err != nil {
		log.Fatalf("failed to retrieve env variables, %v", err)
	}
	if err := Run(cfg); err != nil {
		log.Fatal("error running grpc server ", err)
	}
}

type Office struct {
	Id     uuid.UUID `json:"id" gorm:"type:uuid;default:uuid_generate_v4();primaryKey"`
	Name   string    `json:"name" gorm:"name"`
	Adress string    `json:"adress" gorm:"adress"`
	Clercs []Clerc   `json:"clercs" gorm:"foreignKey:OfficeId;references:Id;constraint:OnDelete:CASCADE"`
}

type Clerc struct {
	Id       uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
	Office   Office    `gorm:"foreignKey:OfficeId"`
	OfficeId uuid.UUID
}

func InitDb(cfg Config) (*gorm.DB, error) {
	dsn := fmt.Sprintf(
		"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
		cfg.PgHost, cfg.PgUser, cfg.PgPwd, cfg.PgDBName, cfg.PgPort,
	)
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("Cannot to Connect DataBase", err)
	}
	db.AutoMigrate(&Office{}, &Clerc{})
	return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}

type OfficeRepository interface {
	Create(ctx context.Context, o *Office) error
	Get(ctx context.Context, id uuid.UUID) (*Office, error)
	GetList(ctx context.Context) ([]*Office, error)
	Update(ctx context.Context, o *Office) error
	Delete(ctx context.Context, id uuid.UUID) error
}

type ClercRepository interface {
	Create(ctx context.Context, c *Clerc) error
	GetList(ctx context.Context) ([]*Clerc, error)
}

type ClercStorage struct {
	db *gorm.DB
}

func NewCs(db *gorm.DB) *ClercStorage {
	return &ClercStorage{
		db: db,
	}
}

func (cs *ClercStorage) Create(ctx context.Context, clerc *Clerc) error {
	return cs.db.WithContext(ctx).Create(clerc).Error
}

func (cs *ClercStorage) GetList(ctx context.Context) ([]*Clerc, error) {
	clercs := []*Clerc{}
	err := cs.db.WithContext(ctx).Find(&clercs).Error
	return clercs, err
}

type OfficeStorage struct {
	db *gorm.DB
}

func NewOs(db *gorm.DB) *OfficeStorage {
	return &OfficeStorage{
		db: db,
	}
}

func (os *OfficeStorage) Create(ctx context.Context, clerc *Office) error {
	return os.db.WithContext(ctx).Create(clerc).Error
}

func (os *OfficeStorage) Get(ctx context.Context, id uuid.UUID) (*Office, error) {
	c := new(Office)
	err := os.db.WithContext(ctx).First(c, id).Error
	return c, err
}

func (os *OfficeStorage) GetList(ctx context.Context) ([]*Office, error) {
	offices := []*Office{}
	err := os.db.WithContext(ctx).Find(&offices).Error
	return offices, err
}

func (os *OfficeStorage) Update(ctx context.Context, office *Office) error {
	return os.db.WithContext(ctx).Save(office).Error
}

func (os *OfficeStorage) Delete(ctx context.Context, id uuid.UUID) error {
	c := new(Office)
	return os.db.WithContext(ctx).Where("id = ?", id).Delete(c).Error
}

type Service struct {
	officerep OfficeRepository
	clercrep  ClercRepository
	config    Config
}

func NewServ(cfg Config, officerep OfficeRepository, clercrep ClercRepository) *Service {
	return &Service{
		config:    cfg,
		officerep: officerep,
		clercrep:  clercrep,
	}
}

func (s *Service) GetHandler() http.Handler {
	router := mux.NewRouter()
	router.HandleFunc("/service2/office/post", s.Post).Methods(http.MethodPost)
	router.HandleFunc("/service2/office/get", s.Get).Methods(http.MethodGet)
	router.HandleFunc("/service2/office/getall", s.GetAll).Methods(http.MethodGet)
	router.HandleFunc("/service2/office/put", s.Put).Methods(http.MethodPut)
	router.HandleFunc("/service2/office/delete", s.Delete).Methods(http.MethodDelete)
	router.HandleFunc("/service2/office/patch", s.Patch).Methods(http.MethodPatch)
	return router
}

type PostRequest struct {
	Name   string `json:"name"`
	Adress string `json:"adress"`
}

func (s *Service) Post(w http.ResponseWriter, r *http.Request) {
	req := &PostRequest{}
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		log.Println("Не удалось разкодировать запрос", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	o := Office{
		Name:   req.Name,
		Adress: req.Adress,
	}
	err := s.officerep.Create(r.Context(), &o)
	if err != nil {
		log.Println("Не удалось создать офис", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(o)
}

type ClercResponse struct {
	Clercs []ClercReq `json:"clerks"`
}

type ClercReq struct {
	Id       string `json:"id"`
	Name     string `json:"name"`
	Post     string `json:"post"`
	OfficeId string `json:"office_id"`
}

type GetResponse struct {
	Id     string     `json:"id"`
	Name   string     `json:"name"`
	Adress string     `json:"adress"`
	Clercs []ClercReq `json:"clercs"`
}

func (s *Service) Get(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	uuid, err := uuid.Parse(id)
	if err != nil {
		log.Println("Не удалось конвертировать строку в uuid", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	o, err := s.officerep.Get(r.Context(), uuid)
	if err != nil {
		log.Println("Не удалось получить запись офиса", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	url := "http://nginx/service1/clerc/getall"
	response, err := http.Get(url)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer response.Body.Close()
	/*body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println("Не удалось прочитать body из Get response с первого сервиса", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}*/
	var clercsreq []ClercReq
	if err := json.NewDecoder(response.Body).Decode(&clercsreq); err != nil {
		log.Println("Не удалось разпарсить ответ с 1 сервиса", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	//var clerksResponse ClercResponse
	/*err = json.Unmarshal(body, clercsreq)
	if err != nil {
		log.Println("Не удалось разпарсить ответ с 1 сервиса", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}*/

	cs, err := s.clercrep.GetList(r.Context())
	if err != nil {
		log.Println("Не удалось получить записи клерков из бд", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	clercsid := make([]string, len(cs))
	for _, c := range cs {
		if o.Id == c.OfficeId {
			clercsid = append(clercsid, c.Id.String())
		}
	}

	clercs := make([]ClercReq, len(o.Clercs))
	for _, clercid := range clercsid {
		for _, c := range clercsreq {
			if clercid == c.Id {
				clercs = append(clercs, c)
			}
		}
	}

	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(&GetResponse{
		Id:     o.Id.String(),
		Name:   o.Name,
		Adress: o.Adress,
		Clercs: clercs,
	})
}

func (s *Service) GetAll(w http.ResponseWriter, r *http.Request) {
	offices, err := s.officerep.GetList(r.Context())
	if err != nil {
		log.Println("Не удалось получить записи офисов из бд", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	clercs, err := s.clercrep.GetList(r.Context())
	if err != nil {
		log.Println("Не удалось получить записи клерков из бд", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	//log.Println(clercs)
	for i, o := range offices {
		for _, c := range clercs {
			if o.Id == c.OfficeId {
				offices[i].Clercs = append(offices[i].Clercs, *c)
			}
		}
	}
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(offices)
}

type PatchRequest struct {
	OfficeId string `json:"office_id"`
	ClercId  string `json:"clerc_id"`
}

func (s *Service) Patch(w http.ResponseWriter, r *http.Request) {
	log.Println("Использована функция добавления клерка в офис")
	req := &PatchRequest{}
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		log.Println("Не удалось разкодировать запрос", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	log.Println(req)
	uuidOffice, err := uuid.Parse(req.OfficeId)
	if err != nil {
		log.Println("Не удалось преобразовать строку в uuid", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	uuidClerc, err := uuid.Parse(req.ClercId)
	if err != nil {
		log.Println("Не удалось преобразовать строку в uuid", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	log.Println(uuidOffice)
	o, err := s.officerep.Get(r.Context(), uuidOffice)
	if err != nil {
		log.Println("Не удалось получить запись офиса из бд", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	log.Println(*o)
	clerc := &Clerc{
		Id:     uuidClerc,
		Office: *o,
	}
	/*o.Clercs = append(o.Clercs, *clerc)
	err = s.officerep.Update(r.Context(), o)
	if err != nil {
		log.Println("Не удалось обновить информации об офисе")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}*/
	err = s.clercrep.Create(r.Context(), clerc)
	if err != nil {
		log.Println("Не удалось создать запись о клерке", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	log.Println(*clerc)
	w.WriteHeader(http.StatusOK)
}

type PutRequest struct {
	Id     string `json:"id"`
	Name   string `json:"name"`
	Adress string `json:"adress"`
}

func (s *Service) Put(w http.ResponseWriter, r *http.Request) {
	req := &PutRequest{}
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		log.Println("Не удалось разкодировать запрос", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	uuid, err := uuid.Parse(req.Id)
	if err != nil {
		log.Println("Не удалось преобразовать строку в uuid", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	o, err := s.officerep.Get(r.Context(), uuid)
	if err != nil {
		log.Println("Не удалось получить запись офиса из бд", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	o.Name = req.Name
	o.Adress = req.Adress
	err = s.officerep.Update(r.Context(), o)
	if err != nil {
		log.Println("Не удалось обновить запись об офисе", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	o2, err := s.officerep.Get(r.Context(), uuid)
	if err != nil {
		log.Println("Не удалось получить запись офиса из бд", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(o2)
}

type DeleteRequest struct {
	Id string `json:"id"`
}

func (s *Service) Delete(w http.ResponseWriter, r *http.Request) {
	req := &DeleteRequest{}
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		log.Println("Не удалось разкодировать запрос", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	uuid, err := uuid.Parse(req.Id)
	if err != nil {
		log.Println("Не удалось конвертировать строку в uuid", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	err = s.officerep.Delete(r.Context(), uuid)
	if err != nil {
		log.Println("Не удалось удалить запись об офисе", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func Run(cfg Config) error {
	db, err := InitDb(cfg)
	if err != nil {
		return err
	}

	serv := NewServ(cfg, NewOs(db), NewCs(db))
	s := &http.Server{
		Addr:    ":13998", //"0.0.0.0:%d", cfg.Port),
		Handler: serv.GetHandler(),
	}
	s.SetKeepAlivesEnabled(true)
	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		log.Printf("starting http server at %d", cfg.Port)
		if err := s.ListenAndServe(); err != nil {
			log.Fatal(err)
		}

	}()

	gracefullyShutdown(ctx, cancel, s)

	return nil

}

func gracefullyShutdown(ctx context.Context, cancel context.CancelFunc, server *http.Server) {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	defer signal.Stop(ch)
	<-ch
	if err := server.Shutdown(ctx); err != nil {
		log.Print(err)
	}
	cancel()
}