package admin

import (
	"encoding/json"
	"log"
	"net/http"
	"time"

	"gitea.local/admin/hspguard/internal/repository"
	"gitea.local/admin/hspguard/internal/util"
	"gitea.local/admin/hspguard/internal/web"
	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
)

type ApiServiceDTO struct {
	ID           uuid.UUID `json:"id"`
	ClientID     string    `json:"client_id"`
	Name         string    `json:"name"`
	Description  *string   `json:"description"`
	RedirectUris []string  `json:"redirect_uris"`
	Scopes       []string  `json:"scopes"`
	GrantTypes   []string  `json:"grant_types"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
	IsActive     bool      `json:"is_active"`
}

func NewApiServiceDTO(service repository.ApiService) ApiServiceDTO {
	return ApiServiceDTO{
		ID:           service.ID,
		ClientID:     service.ClientID,
		Name:         service.Name,
		Description:  service.Description,
		RedirectUris: service.RedirectUris,
		Scopes:       service.Scopes,
		GrantTypes:   service.GrantTypes,
		CreatedAt:    service.CreatedAt,
		UpdatedAt:    service.UpdatedAt,
		IsActive:     service.IsActive,
	}
}

func (h *AdminHandler) GetApiServices(w http.ResponseWriter, r *http.Request) {
	services, err := h.repo.ListApiServices(r.Context())
	if err != nil {
		log.Println("ERR: Failed to list api services from db:", err)
		web.Error(w, "failed to get api services", http.StatusInternalServerError)
		return
	}

	apiServices := make([]ApiServiceDTO, 0)

	for _, service := range services {
		apiServices = append(apiServices, NewApiServiceDTO(service))
	}

	type Response struct {
		Items []ApiServiceDTO `json:"items"`
		Count int             `json:"count"`
	}

	encoder := json.NewEncoder(w)

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(Response{
		Items: apiServices,
		Count: len(apiServices),
	}); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

type AddServiceRequest struct {
	Name         string   `json:"name"`
	Description  string   `json:"description"`
	RedirectUris []string `json:"redirect_uris"`
	Scopes       []string `json:"scopes"`
	GrantTypes   []string `json:"grant_types"`
	IsActive     bool     `json:"is_active"`
}

type ApiServiceCredentials struct {
	ClientId     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

func (h *AdminHandler) AddApiService(w http.ResponseWriter, r *http.Request) {
	var req AddServiceRequest

	decoder := json.NewDecoder(r.Body)

	if err := decoder.Decode(&req); err != nil {
		web.Error(w, "failed to parse request body", http.StatusBadRequest)
		return
	}

	if req.Name == "" {
		web.Error(w, "name is required for an api service", http.StatusBadRequest)
		return
	}

	clientId, err := util.GenerateClientID()
	if err != nil {
		web.Error(w, "failed to generate client id", http.StatusInternalServerError)
		return
	}

	clientSecret, err := util.GenerateClientSecret()
	if err != nil {
		web.Error(w, "failed to generate client secret", http.StatusInternalServerError)
		return
	}

	hashSecret, err := util.HashPassword(clientSecret)
	if err != nil {
		web.Error(w, "failed to create client secret", http.StatusInternalServerError)
		return
	}

	params := repository.CreateApiServiceParams{
		ClientID:     clientId,
		ClientSecret: hashSecret,
		Name:         req.Name,
		RedirectUris: req.RedirectUris,
		Scopes:       req.Scopes,
		GrantTypes:   req.GrantTypes,
		IsActive:     req.IsActive,
	}

	if req.Description != "" {
		params.Description = &req.Description
	}

	service, err := h.repo.CreateApiService(r.Context(), params)
	if err != nil {
		web.Error(w, "failed to create new api service", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	service.ClientSecret = clientSecret

	type Response struct {
		Service     ApiServiceDTO         `json:"service"`
		Credentials ApiServiceCredentials `json:"credentials"`
	}

	encoder := json.NewEncoder(w)

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(Response{
		Service: NewApiServiceDTO(service),
		Credentials: ApiServiceCredentials{
			ClientId:     service.ClientID,
			ClientSecret: service.ClientSecret,
		},
	}); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

func (h *AdminHandler) GetApiService(w http.ResponseWriter, r *http.Request) {
	serviceId := chi.URLParam(r, "id")
	parsed, err := uuid.Parse(serviceId)
	if err != nil {
		web.Error(w, "service id provided is not a valid uuid", http.StatusBadRequest)
		return
	}

	service, err := h.repo.GetApiServiceId(r.Context(), parsed)
	if err != nil {
		web.Error(w, "service with provided id not found", http.StatusNotFound)
		return
	}

	encoder := json.NewEncoder(w)

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(NewApiServiceDTO(service)); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

func (h *AdminHandler) RegenerateApiServiceSecret(w http.ResponseWriter, r *http.Request) {
	serviceId := chi.URLParam(r, "id")
	parsed, err := uuid.Parse(serviceId)
	if err != nil {
		web.Error(w, "provided service id is not valid", http.StatusBadRequest)
		return
	}

	service, err := h.repo.GetApiServiceId(r.Context(), parsed)
	if err != nil {
		web.Error(w, "service with provided id not found", http.StatusNotFound)
		return
	}

	clientSecret, err := util.GenerateClientSecret()
	if err != nil {
		web.Error(w, "failed to generate client secret", http.StatusInternalServerError)
		return
	}

	if err := h.repo.UpdateClientSecret(r.Context(), repository.UpdateClientSecretParams{
		ClientID:     service.ClientID,
		ClientSecret: clientSecret,
	}); err != nil {
		web.Error(w, "failed to update client secret for service", http.StatusInternalServerError)
		return
	}

	encoder := json.NewEncoder(w)

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(ApiServiceCredentials{
		ClientId:     service.ClientID,
		ClientSecret: clientSecret,
	}); err != nil {
		web.Error(w, "failed to send credentials", http.StatusInternalServerError)
	}
}

type UpdateApiServiceRequest struct {
	Name         string   `json:"name"`
	Description  string   `json:"description"`
	RedirectUris []string `json:"redirect_uris"`
	Scopes       []string `json:"scopes"`
	GrantTypes   []string `json:"grant_types"`
}

func (h *AdminHandler) UpdateApiService(w http.ResponseWriter, r *http.Request) {
	serviceId := chi.URLParam(r, "id")
	parsed, err := uuid.Parse(serviceId)
	if err != nil {
		web.Error(w, "provided service id is not valid", http.StatusBadRequest)
		return
	}

	var req UpdateApiServiceRequest

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&req); err != nil {
		web.Error(w, "missing required fields to update service", http.StatusBadRequest)
		return
	}

	if req.Name == "" {
		web.Error(w, "service name is required", http.StatusBadRequest)
		return
	}

	if len(req.Scopes) == 0 {
		web.Error(w, "at least 1 scope is required", http.StatusBadRequest)
		return
	}

	service, err := h.repo.GetApiServiceId(r.Context(), parsed)
	if err != nil {
		web.Error(w, "service with provided id not found", http.StatusNotFound)
		return
	}

	updated, err := h.repo.UpdateApiService(r.Context(), repository.UpdateApiServiceParams{
		ClientID:     service.ClientID,
		Name:         req.Name,
		Description:  &req.Description,
		RedirectUris: req.RedirectUris,
		Scopes:       req.Scopes,
		GrantTypes:   req.GrantTypes,
	})
	if err != nil {
		web.Error(w, "failed to update api service", http.StatusInternalServerError)
		return
	}

	encoder := json.NewEncoder(w)

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(NewApiServiceDTO(updated)); err != nil {
		web.Error(w, "failed to send updated api service", http.StatusInternalServerError)
	}
}

func (h *AdminHandler) ToggleApiService(w http.ResponseWriter, r *http.Request) {
	var err error

	serviceId := chi.URLParam(r, "id")
	parsed, err := uuid.Parse(serviceId)
	if err != nil {
		web.Error(w, "provided service id is not valid", http.StatusBadRequest)
		return
	}

	service, err := h.repo.GetApiServiceId(r.Context(), parsed)
	if err != nil {
		web.Error(w, "service with provided id not found", http.StatusNotFound)
		return
	}

	if service.IsActive {
		log.Println("INFO: Service is active. Deactivating...")
		err = h.repo.DeactivateApiService(r.Context(), service.ClientID)
	} else {
		log.Println("INFO: Service is inactive. Activating...")
		err = h.repo.ActivateApiService(r.Context(), service.ClientID)
	}

	if err != nil {
		log.Printf("ERR: Failed to toggle api service (cid: %s): %v\n", service.ClientID, err)
		web.Error(w, "failed to toggle api service", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
}
