package models

import (
	"context"
	"encoding/json"
	"net/url"
	"strings"
	"time"

	"github.com/NyaaPantsu/nyaa/utils/log"
	"github.com/NyaaPantsu/nyaa/utils/oauth2/client"
	"github.com/ory/fosite"
	"github.com/pkg/errors"
)

const (
	TableOpenID  = "oidc"
	TableAccess  = "access"
	TableRefresh = "refresh"
	TableCode    = "code"
)

const oauth_prefix = "hydra_oauth2_"

type OauthAbstract struct {
	Signature    string    `gorm:"column:signature;primary_key;not null"`
	RequestID    string    `gorm:"column:request_id;not null"`
	RequestedAt  time.Time `gorm:"column:requested_at;not null;type:timestamp;default:CURRENT_TIMESTAMP"`
	ClientID     string    `gorm:"column:client_id;not null"`
	Scope        string    `gorm:"column:scope;not null"`
	GrantedScope string    `gorm:"column:granted_scope;not null"`
	FormData     string    `gorm:"column:form_data;not null"`
	SessionData  []byte    `gorm:"column:session_data;not null"`
}

type OpenID struct {
	OauthAbstract
}

type Access struct {
	OauthAbstract
}

type Code struct {
	OauthAbstract
}

type Refresh struct {
	OauthAbstract
}

func (o OpenID) TableName() string {
	return oauth_prefix + TableOpenID
}

func (a Access) TableName() string {
	return oauth_prefix + TableAccess
}

func (r Refresh) TableName() string {
	return oauth_prefix + TableRefresh
}

func (c Code) TableName() string {
	return oauth_prefix + TableCode
}

func (s *OauthAbstract) ToRequest(session fosite.Session, cm Manager) (*fosite.Request, error) {
	if session != nil {
		if err := json.Unmarshal(s.SessionData, session); err != nil {
			return nil, errors.WithStack(err)
		}
	} else {
		log.Debugf("Got an empty session in toRequest")
	}

	c, err := cm.GetClient(context.Background(), s.ClientID)
	if err != nil {
		return nil, err
	}

	val, err := url.ParseQuery(s.FormData)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	r := &fosite.Request{
		ID:            s.RequestID,
		RequestedAt:   s.RequestedAt,
		Client:        c,
		Scopes:        fosite.Arguments(strings.Split(s.Scope, "|")),
		GrantedScopes: fosite.Arguments(strings.Split(s.GrantedScope, "|")),
		Form:          val,
		Session:       session,
	}

	return r, nil
}

type Manager interface {
	Storage

	Authenticate(id string, secret []byte) (*client.Client, error)
}

type Storage interface {
	fosite.Storage

	CreateClient(c *client.Client) error

	UpdateClient(c *client.Client) error

	DeleteClient(id string) error

	GetClients() (map[string]client.Client, error)

	GetConcreteClient(id string) (*client.Client, error)
}
