package handlers

import (
	"encoding/json"
	"errors"
	"git.insit.tech/psa/rtsp_reader-writer/reader/internal/config"
	"github.com/golang-jwt/jwt/v5"
	"github.com/sirupsen/logrus"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

// Структура HTTP-запроса на регистрацию пользователя
type registerRequest struct {
	Email    string `json:"email"`
	Name     string `json:"name"`
	Password string `json:"password"`
}

// Структура HTTP-запроса на вход в аккаунт
type loginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

// Структура HTTP-ответа на вход в аккаунт
// В ответе содержится JWT-токен авторизованного пользователя
type loginResponse struct {
	AccessToken string `json:"access_token"`
}

var (
	ErrBadCredentials = errors.New("email or password is incorrect")
)

// Структура HTTP-ответа с информацией о пользователе
type ProfileResponse struct {
	Email string `json:"email"`
	Name  string `json:"name"`
}

type VideoRequest struct {
	Date      string `json:"date"`
	StartTime string `json:"start_time"`
	EndTime   string `json:"end_time"`
}

type ListVodsResponse struct {
	EstimatedCount int                 `json:"estimated_count"` // Estimated total number of records for the query.
	Vods           []string            `json:"vods"`            // List of vods.
	Files          map[string][]string `json:"files"`           // List files and folders of a specific VOD location.
}

type ConfigVodsResponse struct {
	Prefix    string   `json:"prefix"`   // The unique name of VOD location.
	AutoMbr   bool     `json:"auto_mbr"` // Turns on automatic creation of a multi-bitrate HLS playlist from several files with different bitrates.
	Disabled  bool     `json:"disabled"` // Whether this VOD location is disabled.
	Protocols struct { // Configuraton of play protocols.
		Hls       bool `json:"hls"`       // Whether to allow or deny an HLS stream playback.
		Cmaf      bool `json:"cmaf"`      // Whether to allow or deny an LL-HLS stream playback.
		Dash      bool `json:"dash"`      // Whether to allow or deny a DASH stream playback.
		Player    bool `json:"player"`    // Whether to allow or deny playback in embed.html.
		Mss       bool `json:"mss"`       // Whether to allow or deny an MSS stream playback.
		Rtmp      bool `json:"rtmp"`      // Whether to allow or deny an RTMP stream playback.
		Rtsp      bool `json:"rtsp"`      // Whether to allow or deny an RTSP stream playback.
		M4F       bool `json:"m4f"`       // Whether to allow or deny an M4F stream playback.
		M4S       bool `json:"m4s"`       // Whether to allow or deny an M4S stream playback.
		Mseld     bool `json:"mseld"`     // Whether to allow or deny an MSE-LD stream playback.
		Tshttp    bool `json:"tshttp"`    // Whether to allow or deny an MPEG-TS stream playback over HTTP(S).
		Webrtc    bool `json:"webrtc"`    // Whether to allow or deny an WebRTC stream playback.
		Srt       bool `json:"srt"`       // Whether to allow or deny an SRT stream playback.
		Shoutcast bool `json:"shoutcast"` // Whether to allow or deny a SHOUTcast/Icecast stream playback.
		Mp4       bool `json:"mp4"`       // Whether to allow or deny an MP4 file download over HTTP(S).
		Jpeg      bool `json:"jpeg"`      // Whether to allow or deny delivering JPEG thumbnails over HTTP(S).
		Api       bool `json:"api"`       // Whether to allow or deny API requests.
	} `json:"protocols"`
	SegmentDuration int    `json:"segment_duration"` // The time, in seconds, of the segment duration. Used for the protocols like HLS or DASH.
	AddAudioOnly    bool   `json:"add_audio_only"`   // Whether to add an audio-only version of an HLS stream. Used to create App Store compliant HLS streams to deliver the content to Apple iOS devices. Add audio-only HLS playlist to variant MBR playlist for iOS compliant streaming.
	Provider        string `json:"provider"`         // Human-readable name of the content provider. Applicable to MPEG-TS.
}

type SingleVodsResponse struct {
	Name      string                  `json:"name"`
	Prefix    string                  `json:"prefix"`
	Url       string                  `json:"url"`
	Folder    string                  `json:"folder"`
	Bytes     int64                   `json:"bytes"`
	MediaInfo MediaSingleVodsResponse `json:"media_info"`
}

type MediaSingleVodsResponse struct {
	Tracks   map[string]string `json:"tracks"`
	Duration int               `json:"duration"`
	Provider string            `json:"provider"`
	Title    string            `json:"title"`
}

// Обработчик HTTP-запросов на регистрацию пользователя
func Register(w http.ResponseWriter, r *http.Request) {
	regReq := registerRequest{}

	if err := json.NewDecoder(r.Body).Decode(&regReq); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Проверяем, что пользователь с таким email еще не зарегистрирован
	if _, exists := config.Storage.Users[regReq.Email]; exists {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Сохраняем в память нового зарегистрированного пользователя
	config.Storage.Users[regReq.Email] = config.User{
		Email:    regReq.Email,
		Name:     regReq.Name,
		Password: regReq.Password,
	}

	w.WriteHeader(http.StatusCreated)
}

// Обработчик HTTP-запросов на вход в аккаунт
func Login(w http.ResponseWriter, r *http.Request) {
	regReq := loginRequest{}
	if err := json.NewDecoder(r.Body).Decode(&regReq); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Ищем пользователя в памяти приложения по электронной почте
	user, exists := config.Storage.Users[regReq.Email]
	// Если пользователь не найден, возвращаем ошибку
	if !exists {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	// Если пользователь найден, но у него другой пароль, возвращаем ошибку
	if user.Password != regReq.Password {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Генерируем JWT-токен для пользователя,
	// который он будет использовать в будущих HTTP-запросах

	// Генерируем полезные данные, которые будут храниться в токене
	payload := jwt.MapClaims{
		"sub": user.Email,
		"exp": time.Now().Add(time.Hour * 24).Unix(),
	}

	// Создаем новый JWT-токен и подписываем его по алгоритму HS256
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)

	t, err := token.SignedString(config.JwtSecretKey)
	if err != nil {
		logrus.WithError(err).Error("JWT token signing")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	res, err := json.Marshal(loginResponse{AccessToken: t})
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if _, err = w.Write(res); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	//w.WriteHeader(http.StatusOK)
}

// Inactive5Minutes checks if a folder has files that was not created or modified for last 5 minutes.
func Inactive5Minutes(entries []os.DirEntry) (bool, error) {
	threshold := time.Now().Add(-5 * time.Minute)

	disabled := true

	for _, entry := range entries {
		info, err := entry.Info()
		if err != nil {
			return true, errors.New("Info error: " + err.Error())
		}

		if info.ModTime().After(threshold) {
			disabled = false
			return disabled, nil
		}
	}

	return true, nil
}

// RecDurationMilliseconds calculates the difference between first existing rec file and the last one in milliseconds.
func RecDurationMilliseconds(entries []os.DirEntry) (int, error) {
	// Find last file and first file; get timestamps.
	lastFile := entries[len(entries)-1].Name()
	lastTime := strings.Split(lastFile, "_")[0]

	firstFile := entries[0].Name()
	firstTime := strings.Split(firstFile, "_")[0]

	// Convert string timestamps to int.
	lastTimeInt, err := strconv.Atoi(lastTime)
	if err != nil {
		return 0, errors.New("convert last time error: " + err.Error())
	}

	firstTimeInt, err := strconv.Atoi(firstTime)
	if err != nil {
		return 0, errors.New("convert first time error: " + err.Error())
	}

	// Calculate the difference.
	difference := lastTimeInt - firstTimeInt

	return difference * 1000, nil
}

// ListVodsHandlerLogic implements main logic of handler ListVodsHandler.
func ListVodsHandlerLogic() (ListVodsResponse, error) {
	// Read directory.
	entries, err := os.ReadDir(config.DirData)
	if err != nil {
		return ListVodsResponse{}, err
	}

	// Filter only folders.
	var dirs []string
	for _, entry := range entries {
		if entry.IsDir() {
			dirs = append(dirs, entry.Name())
		}
	}

	// Prepare the Response.
	VodsRes := ListVodsResponse{
		EstimatedCount: len(dirs),
		Vods:           dirs,
	}

	return VodsRes, nil
}

// ListVodsHandler returns the list of VOD locations.
//
// This method allows to get the list of all VOD locations. VOD location is a virtual filepath used to place files for
// VOD (Video on Demand) broadcasting.
func ListVodsHandler() http.Handler {
	log.Println("before return ListVodsHandler")

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("hello fron ListVodsHandler")

		// Prepare the Response.
		VodsRes, err := ListVodsHandlerLogic()
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		// Write header and code response.
		w.Header().Set("Content-Type", "application/json")
		if err = json.NewEncoder(w).Encode(VodsRes); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		log.Println("hello fron ListVodsHandlerLogic: StatusOK")
	})
}
