package auth

import (
	"context"
	"errors"

	"gitlab.com/dynaweb/ains-microservices/pkg/delima"
	"gitlab.com/dynaweb/ains-microservices/pkg/jwt"
	"gitlab.com/dynaweb/ains-microservices/pkg/oauth2"
)

var (
	ErrRoleNotAllowed = errors.New("auth: role not allowed")
	ErrMissingEmail   = errors.New("auth: missing email")
)

type Service struct {
	users    UserRepo
	schools  SchoolRepo
	delima   *delima.Client
	tokens   *jwt.Manager
	denylist Denylist
	fallback string
}

func NewService(users UserRepo, schools SchoolRepo, dclient *delima.Client, tokens *jwt.Manager, denylist Denylist, fallbackRedirect string) *Service {
	return &Service{
		users:    users,
		schools:  schools,
		delima:   dclient,
		tokens:   tokens,
		denylist: denylist,
		fallback: fallbackRedirect,
	}
}

type Session struct {
	UserID       int64
	Role         string
	State        string
	PPD          string
	School       string
	AccessToken  string
	RefreshToken string
	Redirect     string
}

func (s *Service) CompleteLogin(ctx context.Context, info *oauth2.UserInfo, accessToken string) (*Session, error) {
	if info == nil || info.Email == "" {
		return nil, ErrMissingEmail
	}

	duser, err := s.delimaLookup(ctx, info.Email)
	if err != nil {
		return nil, err
	}

	role := ResolveRole(info.Email, duser)
	if role == "" || !AllowedRole(role) {
		return &Session{Redirect: "/web/auth-failed"}, ErrRoleNotAllowed
	}

	school, scopeState, scopePPD := s.resolveSchool(ctx, role, duser)

	user := &User{
		Username: info.Email,
		Fullname: info.Name,
		Email:    info.Email,
		ImgURL:   info.Picture,
		Provider: "google.com",
		Role:     role,
		State:    scopeState,
		PPD:      scopePPD,
	}
	if school != nil {
		user.School = itoa(school.ID)
	}
	if duser != nil && duser.Classroom != nil {
		user.ClassYear = duser.Classroom.Code
		user.ClassName = duser.Classroom.Name
	}
	_ = accessToken

	saved, err := s.users.Upsert(ctx, user)
	if err != nil {
		return nil, err
	}

	identity := jwt.Identity{
		UserID: saved.ID,
		Role:   saved.Role,
		State:  saved.State,
		PPD:    saved.PPD,
		School: saved.School,
	}
	access, err := s.tokens.IssueAccess(identity)
	if err != nil {
		return nil, err
	}
	refresh, err := s.tokens.IssueRefresh(identity)
	if err != nil {
		return nil, err
	}

	return &Session{
		UserID:       saved.ID,
		Role:         saved.Role,
		State:        saved.State,
		PPD:          saved.PPD,
		School:       saved.School,
		AccessToken:  access,
		RefreshToken: refresh,
		Redirect:     "/web/" + saved.Role + "-dashboard",
	}, nil
}

func (s *Service) delimaLookup(ctx context.Context, email string) (*delima.User, error) {
	user, err := s.delima.User(ctx, email)
	if err == nil {
		return user, nil
	}
	if errors.Is(err, delima.ErrNotConfigured) || errors.Is(err, delima.ErrUserNotFound) {
		return nil, nil
	}
	return nil, nil
}

func (s *Service) resolveSchool(ctx context.Context, role string, duser *delima.User) (*School, string, string) {
	var school *School
	if duser != nil && duser.Organization != nil && duser.Organization.Code != "" {
		school = s.findOrCreateSchool(ctx, duser)
	}

	state := ""
	ppd := ""
	if school != nil {
		state = school.State
		ppd = school.PPD
	}

	if duser != nil && duser.Organization != nil && (role == RolePPD || role == RoleJPN) {
		if district := duser.Organization.District; district != "" {
			ppd = "PPD " + district
		}
		if duser.Organization.State != "" {
			state = NormalizeState(duser.Organization.State)
		}
	}
	return school, state, ppd
}

func (s *Service) findOrCreateSchool(ctx context.Context, duser *delima.User) *School {
	org := duser.Organization
	existing, err := s.schools.FindByCode(ctx, org.Code)
	if err == nil && existing != nil {
		return existing
	}

	school := &School{
		Code:     org.Code,
		Name:     org.Name,
		Email:    org.Email,
		Address:  org.Address,
		City:     org.City,
		Postcode: org.Postcode,
		PPD:      org.District,
		State:    NormalizeState(org.State),
		Type:     org.Type,
	}
	created, err := s.schools.Insert(ctx, school)
	if err != nil {
		return nil
	}
	return created
}

func itoa(n int64) string {
	if n == 0 {
		return ""
	}
	neg := n < 0
	if neg {
		n = -n
	}
	var buf [20]byte
	i := len(buf)
	for n > 0 {
		i--
		buf[i] = byte('0' + n%10)
		n /= 10
	}
	if neg {
		i--
		buf[i] = '-'
	}
	return string(buf[i:])
}
