package social

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/graph-gophers/graphql-go"
)

const Schema = `
	schema {
		query: Query
	}
	
	type Query {
		admin(id: ID!, role: Role = ADMIN): Admin!
		user(id: ID!): User!
		search(text: String!): [SearchResult]!
	}
	
	interface Admin {
		id: ID!
		name: String!
		role: Role!
	}

	interface Person {
		name: String!
	}

	scalar Time	

	type User implements Admin & Person {
		id: ID!
		name: String!
		email: String!
		role: Role!
		phone: String!
		address: [String!]
		friends(page: Pagination): [User]
		createdAt: Time!
	}

	input Pagination {
	  	first: Int
	  	last: Int
	}
	
	enum Role {
		ADMIN
		USER
	}

	union SearchResult = User
`

type page struct {
	First *float64
	Last  *float64
}

type admin interface {
	ID() graphql.ID
	Name() string
	Role() string
}

type adminResolver struct {
	admin
}

func (r *adminResolver) ToUser() (*user, bool) {
	n, ok := r.admin.(user)
	return &n, ok
}

type searchResult struct {
	result any
}

func (r *searchResult) ToUser() (*user, bool) {
	res, ok := r.result.(*user)
	return res, ok
}

type contact struct {
	Email string
	Phone string
}

type user struct {
	IDField      string
	NameField    string
	RoleField    string
	Address      *[]string
	FriendsField *[]*user
	CreatedAt    graphql.Time
	contact
}

func (u user) ID() graphql.ID {
	return graphql.ID(u.IDField)
}

func (u user) Name() string {
	return u.NameField
}

func (u user) Role() string {
	return u.RoleField
}

func (u user) Friends(args struct{ Page *page }) (*[]*user, error) {
	var from int
	numFriends := len(*u.FriendsField)
	to := numFriends

	if args.Page != nil {
		if args.Page.First != nil {
			from = int(*args.Page.First)
			if from > numFriends {
				return nil, errors.New("not enough users")
			}
		}
		if args.Page.Last != nil {
			to = int(*args.Page.Last)
			if to == 0 || to > numFriends {
				to = numFriends
			}
		}
	}

	friends := (*u.FriendsField)[from:to]

	return &friends, nil
}

var users = []*user{
	{
		IDField:   "0x01",
		NameField: "Albus Dumbledore",
		RoleField: "ADMIN",
		Address:   &[]string{"Office @ Hogwarts", "where Horcruxes are"},
		CreatedAt: graphql.Time{Time: time.Now()},
		contact: contact{
			Email: "Albus@hogwarts.com",
			Phone: "000-000-0000",
		},
	},
	{
		IDField:   "0x02",
		NameField: "Harry Potter",
		RoleField: "USER",
		Address:   &[]string{"123 dorm room @ Hogwarts", "456 random place"},
		CreatedAt: graphql.Time{Time: time.Now()},
		contact: contact{
			Email: "harry@hogwarts.com",
			Phone: "000-000-0001",
		},
	},
	{
		IDField:   "0x03",
		NameField: "Hermione Granger",
		RoleField: "USER",
		Address:   &[]string{"233 dorm room @ Hogwarts", "786 @ random place"},
		CreatedAt: graphql.Time{Time: time.Now()},
		contact: contact{
			Email: "hermione@hogwarts.com",
			Phone: "000-000-0011",
		},
	},
	{
		IDField:   "0x04",
		NameField: "Ronald Weasley",
		RoleField: "USER",
		Address:   &[]string{"411 dorm room @ Hogwarts", "981 @ random place"},
		CreatedAt: graphql.Time{Time: time.Now()},
		contact: contact{
			Email: "ronald@hogwarts.com",
			Phone: "000-000-0111",
		},
	},
}

var usersMap = make(map[string]*user)

func init() {
	users[0].FriendsField = &[]*user{users[1]}
	users[1].FriendsField = &[]*user{users[0], users[2], users[3]}
	users[2].FriendsField = &[]*user{users[1], users[3]}
	users[3].FriendsField = &[]*user{users[1], users[2]}
	for _, usr := range users {
		usersMap[usr.IDField] = usr
	}
}

type Resolver struct{}

func (r *Resolver) Admin(ctx context.Context, args struct {
	ID   string
	Role string
},
) (*adminResolver, error) {
	if usr, ok := usersMap[args.ID]; ok {
		if usr.RoleField == args.Role {
			return &adminResolver{*usr}, nil
		}
	}
	err := fmt.Errorf("user with id=%s and role=%s does not exist", args.ID, args.Role)
	return nil, err
}

func (r *Resolver) User(ctx context.Context, args struct{ ID string }) (user, error) {
	if usr, ok := usersMap[args.ID]; ok {
		return *usr, nil
	}
	err := fmt.Errorf("user with id=%s does not exist", args.ID)
	return user{}, err
}

func (r *Resolver) Search(ctx context.Context, args struct{ Text string }) ([]*searchResult, error) {
	var result []*searchResult
	for _, usr := range users {
		if strings.Contains(usr.NameField, args.Text) {
			result = append(result, &searchResult{usr})
		}
	}
	return result, nil
}
