/*
Implements gRPC API server.

Author: Olek
*/
package main

import (
	"context"
	"errors"

	api_common "server/app_comm/api/common"
	api_device "server/app_comm/api/device"
	api_user "server/app_comm/api/user"

	"github.com/jackc/pgx/v5"
)

var (
	ErrNoIndex error = errors.New("no valid index provided for request")
)

// === Device Service ===
type DeviceServiceServer struct {
	api_device.UnimplementedDeviceServiceServer
	App *AppData
}

func (s *DeviceServiceServer) GetFull(ctx context.Context, index *api_common.StrIndex) (*api_device.DeviceAllInfo, error) {
	info := api_device.DeviceAllInfo{}
	var err error
	var row pgx.Row
	var deviceId int32

	switch index.Id.(type) {
	case *api_common.StrIndex_Num: // Indexing by DB ID
		deviceId = index.GetNum()

		row = s.App.DBPool.QueryRow(ctx, `
			SELECT euid, name, fw_version, charge, last_online, latitude, longitude, altitude 
			FROM Devices WHERE id = $1;`,
			deviceId,
		)
		err = row.Scan(
			&info.Euid,
			&info.Name,
			&info.FwVersion,
			&info.Charge,
			&info.LastOnline,
			&info.Latitude,
			&info.Longitude,
			&info.Altitude,
		)
	case *api_common.StrIndex_Name: // Indexing by EUID
		euid := index.GetName()
		info.Euid = euid

		row = s.App.DBPool.QueryRow(ctx, `
			SELECT name, fw_version, charge, last_online, latitude, longitude, altitude, id
			FROM Devices WHERE euid = $1;`,
			euid,
		)
		err = row.Scan(
			&info.Name,
			&info.FwVersion,
			&info.Charge,
			&info.LastOnline,
			&info.Latitude,
			&info.Longitude,
			&info.Altitude,
			&deviceId,
		)
	default: // Zeroed out
		return nil, ErrNoIndex
	}

	if err != nil {
		return &info, err
	}

	// Fetch latest Status
	row = s.App.DBPool.QueryRow(ctx, `
		SELECT internal_status, native_status 
		FROM Statuses 
		WHERE device_id = $1 
		ORDER BY created_at DESC LIMIT 1;`, deviceId,
	)
	var int_status int16
	var nat_status string
	err = row.Scan(&int_status, &nat_status)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return &info, err
	}
	info.InternalStatus = int32(int_status)
	info.NativeStatus = nat_status

	// Fetch paired cards
	row = s.App.DBPool.QueryRow(ctx, `
		SELECT id 
		FROM NFC_Cards 
		WHERE device_id = $1 
		ORDER BY created_at DESC;`, deviceId,
	)
	var card_ids []int32
	err = row.Scan(&card_ids)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return &info, err
	}
	info.CardIds = card_ids

	return &info, nil
}

func (s *DeviceServiceServer) GetInfo(ctx context.Context, index *api_common.StrIndex) (*api_device.DeviceBaseInfo, error) {
	var err error
	var row pgx.Row
	var deviceId int32
	info := api_device.DeviceBaseInfo{}

	switch index.Id.(type) {
	case *api_common.StrIndex_Num:
		row = s.App.DBPool.QueryRow(ctx, "SELECT (id, name, euid, last_online) FROM Devices WHERE id = $1;", index.GetNum())
		err = row.Scan(&deviceId, &info.Name, &info.Euid, &info.LastOnline)
	case *api_common.StrIndex_Name:
		euid := index.GetName()
		info.Euid = euid

		row = s.App.DBPool.QueryRow(ctx, "SELECT (id, name, last_online) FROM Devices WHERE euid = $1;", euid)
		err = row.Scan(&deviceId, &info.Name, &info.LastOnline)
	default:
		return nil, ErrNoIndex
	}

	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return &info, err
	}

	row = s.App.DBPool.QueryRow(ctx, `
		SELECT internal_status 
		FROM Statuses 
		WHERE device_id = $1 
		ORDER BY created_at DESC LIMIT 1;`, deviceId,
	)
	var status int16
	err = row.Scan(&status)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return &info, err
	}

	info.InternalStatus = int32(status)

	return &info, err
}

// === User Service ===
type UserServiceServer struct {
	api_user.UnimplementedUserServiceServer
	App *AppData
}

func (s *UserServiceServer) GetPassword(ctx context.Context, index *api_common.StrIndex) (*api_user.Password, error) {
	var err error
	var row pgx.Row
	info := api_user.Password{}

	switch index.Id.(type) {
	case *api_common.StrIndex_Num:
		row = s.App.DBPool.QueryRow(ctx, "SELECT password FROM Users WHERE id = $1;", index.GetNum())
		err = row.Scan(&info.Password)
	case *api_common.StrIndex_Name:
		row = s.App.DBPool.QueryRow(ctx, "SELECT password FROM Users WHERE name = $1;", index.GetName())
		err = row.Scan(&info.Password)
	default:
		return nil, ErrNoIndex
	}

	return &info, err
}

//TODO: finish
