package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
	"bytes"
	"context"
	"errors"
	"io"
	"net/http"
	"os"
	"path"
	"strings"
	"time"

	"github.com/gabriel-vasile/mimetype"
	"github.com/google/uuid"
	"reichard.io/imagini/graph/generated"
	"reichard.io/imagini/graph/model"
)

func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewMediaItem) (*model.MediaItem, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	// File header placeholder
	fileHeader := make([]byte, 64)

	// Copy headers into the buffer
	if _, err := input.File.File.Read(fileHeader); err != nil {
		return nil, errors.New("Upload Failed")
	}

	// Determine media type
	fileMime := mimetype.Detect(fileHeader)
	contentType := fileMime.String()
	var isVideo bool
	if strings.HasPrefix(contentType, "image/") {
		isVideo = false
	} else if strings.HasPrefix(contentType, "video/") {
		isVideo = true
	} else {
		return nil, errors.New("Upload Failed")
	}

	// Derive Folder & File Path
	mediaItemID := uuid.New().String()
	fileName := mediaItemID + fileMime.Extension()
	folderPath := path.Join("/" + r.Config.DataPath + "/media/" + userID)
	os.MkdirAll(folderPath, 0700)
	filePath := path.Join(folderPath + "/" + fileName)

	// Create File
	f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return nil, errors.New("Upload Failed")
	}
	defer f.Close()

	// Copy header to file
	_, err = io.Copy(f, bytes.NewReader(fileHeader))
	if err != nil {
		return nil, errors.New("Upload Failed")
	}

	// Copy remaining file
	_, err = io.Copy(f, input.File.File)
	if err != nil {
		return nil, errors.New("Upload Failed")
	}

	// Create MediaItem From EXIF Data
	mediaItem, err := mediaItemFromEXIFData(filePath)
	if err != nil {
		return nil, errors.New("Upload Failed")
	}

	// Add Additional MediaItem Fields
	mediaItem.ID = mediaItemID
	mediaItem.UserID = userID
	mediaItem.IsVideo = isVideo
	mediaItem.FileName = fileName
	mediaItem.OrigName = input.File.Filename

	// Create MediaItem in DB
	err = r.DB.CreateMediaItem(mediaItem)
	if err != nil {
		return nil, errors.New("Upload Failed")
	}

	// Success
	return mediaItem, nil
}

func (r *mutationResolver) CreateAlbum(ctx context.Context, input model.NewAlbum) (*model.Album, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	album := &model.Album{
		Name:   input.Name,
		UserID: userID,
	}

	err = r.DB.CreateAlbum(album)
	if err != nil {
		return nil, err
	}

	return album, nil
}

func (r *mutationResolver) CreateTag(ctx context.Context, input model.NewTag) (*model.Tag, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	tag := &model.Tag{
		Name:   input.Name,
		UserID: userID,
	}

	err = r.DB.CreateTag(tag)
	if err != nil {
		return nil, err
	}

	return tag, nil
}

func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
	user := &model.User{
		Email:     input.Email,
		Username:  input.Username,
		FirstName: input.FirstName,
		LastName:  input.LastName,
		Role:      input.Role,
		AuthType:  input.AuthType,
		Password:  input.Password,
	}

	err := r.DB.CreateUser(user)
	if err != nil {
		return nil, err
	}

	return user, nil
}

func (r *queryResolver) Login(ctx context.Context, user string, password string, deviceID *string) (*model.AuthResponse, error) {
	// Acquire Context
	resp, req, err := getContextHTTP(ctx)
	if err != nil {
		return nil, err
	}

	// Clear All Cookies By Default
	accessCookie := http.Cookie{Name: "AccessToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)}
	refreshCookie := http.Cookie{Name: "RefreshToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)}
	http.SetCookie(*resp, &accessCookie)
	http.SetCookie(*resp, &refreshCookie)

	// Do Login
	foundUser, success := r.Auth.AuthenticateUser(user, password)
	if !success {
		return &model.AuthResponse{Result: model.AuthResultFailure}, nil
	}

	// Upsert Device
	foundDevice := model.Device{UserID: foundUser.ID}
	if deviceID != nil {
		parsedDeviceID, err := uuid.Parse(*deviceID)
		if err != nil {
			return &model.AuthResponse{Result: model.AuthResultFailure}, nil
		}
		foundDevice.ID = parsedDeviceID.String()
		count, err := r.DB.Device(&foundDevice)
		if count != 1 || err != nil {
			return &model.AuthResponse{Result: model.AuthResultFailure}, nil
		}
	} else {
		foundDevice.Type = deriveDeviceType(req)
		err := r.DB.CreateDevice(&foundDevice)
		if err != nil {
			return &model.AuthResponse{Result: model.AuthResultFailure}, nil
		}
	}

	// Create Tokens
	accessToken, err := r.Auth.CreateJWTAccessToken(foundUser, foundDevice)
	if err != nil {
		return &model.AuthResponse{Result: model.AuthResultFailure}, nil
	}
	refreshToken, err := r.Auth.CreateJWTRefreshToken(foundUser, foundDevice)
	if err != nil {
		return &model.AuthResponse{Result: model.AuthResultFailure}, nil
	}

	// Set appropriate cookies (TODO: Only for web!)
	accessCookie = http.Cookie{Name: "AccessToken", Value: accessToken, Path: "/", HttpOnly: false}
	refreshCookie = http.Cookie{Name: "RefreshToken", Value: refreshToken, Path: "/", HttpOnly: false}
	http.SetCookie(*resp, &accessCookie)
	http.SetCookie(*resp, &refreshCookie)

	// Only for iOS & Android (TODO: Remove for web! Only cause affected by CORS during development)
	(*resp).Header().Set("X-Imagini-AccessToken", accessToken)
	(*resp).Header().Set("X-Imagini-RefreshToken", refreshToken)

	return &model.AuthResponse{Result: model.AuthResultSuccess, Device: &foundDevice}, nil
}

func (r *queryResolver) Logout(ctx context.Context) (*model.AuthResponse, error) {
	// Acquire Context
	resp, _, err := getContextHTTP(ctx)
	if err != nil {
		return nil, err
	}

	// Clear All Cookies
	accessCookie := http.Cookie{Name: "AccessToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)}
	refreshCookie := http.Cookie{Name: "RefreshToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)}
	http.SetCookie(*resp, &accessCookie)
	http.SetCookie(*resp, &refreshCookie)

	return &model.AuthResponse{Result: model.AuthResultSuccess}, nil
}

func (r *queryResolver) MediaItem(ctx context.Context, id string) (*model.MediaItem, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	mediaItemID, err := uuid.Parse(id)
	if err != nil {
		return nil, errors.New("Invalid ID Format")
	}

	foundMediaItem := &model.MediaItem{ID: mediaItemID.String(), UserID: userID}
	count, err := r.DB.MediaItem(foundMediaItem)
	if err != nil {
		return nil, errors.New("DB Error")
	} else if count != 1 {
		return nil, errors.New("MediaItem Not Found")
	}
	return foundMediaItem, nil
}

func (r *queryResolver) Device(ctx context.Context, id string) (*model.Device, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	deviceID, err := uuid.Parse(id)
	if err != nil {
		return nil, errors.New("Invalid ID Format")
	}

	foundDevice := &model.Device{ID: deviceID.String(), UserID: userID}
	count, err := r.DB.Device(foundDevice)
	if err != nil {
		return nil, errors.New("DB Error")
	} else if count != 1 {
		return nil, errors.New("Device Not Found")
	}
	return foundDevice, nil
}

func (r *queryResolver) Album(ctx context.Context, id string) (*model.Album, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	albumID, err := uuid.Parse(id)
	if err != nil {
		return nil, errors.New("Invalid ID Format")
	}

	foundAlbum := &model.Album{ID: albumID.String(), UserID: userID}
	count, err := r.DB.Album(foundAlbum)
	if err != nil {
		return nil, errors.New("DB Error")
	} else if count != 1 {
		return nil, errors.New("Album Not Found")
	}
	return foundAlbum, nil
}

func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
	userID, err := uuid.Parse(id)
	if err != nil {
		return nil, errors.New("Invalid ID Format")
	}

	foundUser := &model.User{ID: userID.String()}
	count, err := r.DB.User(foundUser)
	if err != nil {
		return nil, errors.New("DB Error")
	} else if count != 1 {
		return nil, errors.New("User Not Found")
	}
	return foundUser, nil
}

func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	tagID, err := uuid.Parse(id)
	if err != nil {
		return nil, errors.New("Invalid ID Format")
	}

	foundTag := &model.Tag{ID: tagID.String(), UserID: userID}
	count, err := r.DB.Tag(foundTag)
	if err != nil {
		return nil, errors.New("DB Error")
	} else if count != 1 {
		return nil, errors.New("Tag Not Found")
	}
	return foundTag, nil
}

func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	foundUser := &model.User{ID: userID}
	count, err := r.DB.User(foundUser)
	if err != nil || count != 1 {
		return nil, errors.New("DB Error")
	}
	return foundUser, nil
}

func (r *queryResolver) MediaItems(ctx context.Context, filter *model.MediaItemFilter, page *model.Page, order *model.Order) (*model.MediaItemResponse, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	resp, pageResponse, err := r.DB.MediaItems(userID, filter, page, order)
	if err != nil {
		return nil, errors.New("DB Error")
	}
	return &model.MediaItemResponse{
		Data: resp,
		Page: &pageResponse,
	}, nil
}

func (r *queryResolver) Devices(ctx context.Context, filter *model.DeviceFilter, page *model.Page, order *model.Order) (*model.DeviceResponse, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	resp, pageResponse, err := r.DB.Devices(userID, filter, page, order)
	if err != nil {
		return nil, errors.New("DB Error")
	}
	return &model.DeviceResponse{
		Data: resp,
		Page: &pageResponse,
	}, nil
}

func (r *queryResolver) Albums(ctx context.Context, filter *model.AlbumFilter, page *model.Page, order *model.Order) (*model.AlbumResponse, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	resp, pageResponse, err := r.DB.Albums(userID, filter, page, order)
	if err != nil {
		return nil, errors.New("Context Error")
	}
	return &model.AlbumResponse{
		Data: resp,
		Page: &pageResponse,
	}, nil
}

func (r *queryResolver) Tags(ctx context.Context, filter *model.TagFilter, page *model.Page, order *model.Order) (*model.TagResponse, error) {
	// Acquire Context
	userID, _, err := getContextIDs(ctx)
	if err != nil {
		return nil, err
	}

	resp, pageResponse, err := r.DB.Tags(userID, filter, page, order)
	if err != nil {
		return nil, errors.New("Context Error")
	}
	return &model.TagResponse{
		Data: resp,
		Page: &pageResponse,
	}, nil
}

func (r *queryResolver) Users(ctx context.Context, filter *model.UserFilter, page *model.Page, order *model.Order) (*model.UserResponse, error) {
	resp, pageResponse, err := r.DB.Users(filter, page, order)
	if err != nil {
		return nil, errors.New("Context Error")
	}
	return &model.UserResponse{
		Data: resp,
		Page: &pageResponse,
	}, nil
}

// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
