// Package main provides FX providers for Authz Service.
// This file creates the service inline to avoid importing internal packages.
package main

import (
	"context"
	"fmt"
	"net"

	authzv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1"
	"git.dcentral.systems/toolz/goplt/internal/ent"
	"git.dcentral.systems/toolz/goplt/internal/ent/userrole"
	"git.dcentral.systems/toolz/goplt/pkg/config"
	"git.dcentral.systems/toolz/goplt/pkg/logger"
	"go.uber.org/fx"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
)

// authzService provides authorization functionality.
type authzService struct {
	client *ent.Client
	logger logger.Logger
}

// hasPermission checks if a user has a specific permission.
func (s *authzService) hasPermission(ctx context.Context, userID, permCode string) (bool, error) {
	// Get user's roles
	userRoles, err := s.client.UserRole.Query().
		Where(userrole.UserID(userID)).
		WithRole(func(rq *ent.RoleQuery) {
			rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
				rpq.WithPermission()
			})
		}).
		All(ctx)
	if err != nil {
		return false, fmt.Errorf("failed to get user roles: %w", err)
	}

	// Check if any role has the permission
	for _, ur := range userRoles {
		role := ur.Edges.Role
		if role == nil {
			continue
		}

		rolePerms := role.Edges.RolePermissions
		for _, rp := range rolePerms {
			perm := rp.Edges.Permission
			if perm != nil && perm.Name == permCode {
				return true, nil
			}
		}
	}

	return false, nil
}

// getUserPermissions returns all permissions for a user.
func (s *authzService) getUserPermissions(ctx context.Context, userID string) ([]*ent.Permission, error) {
	// Get user's roles
	userRoles, err := s.client.UserRole.Query().
		Where(userrole.UserID(userID)).
		WithRole(func(rq *ent.RoleQuery) {
			rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
				rpq.WithPermission()
			})
		}).
		All(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get user roles: %w", err)
	}

	// Collect unique permissions
	permMap := make(map[string]*ent.Permission)
	for _, ur := range userRoles {
		role := ur.Edges.Role
		if role == nil {
			continue
		}

		rolePerms := role.Edges.RolePermissions
		for _, rp := range rolePerms {
			perm := rp.Edges.Permission
			if perm != nil {
				permMap[perm.ID] = perm
			}
		}
	}

	// Convert map to slice
	permissions := make([]*ent.Permission, 0, len(permMap))
	for _, perm := range permMap {
		permissions = append(permissions, perm)
	}

	return permissions, nil
}

// getUserRoles returns all roles for a user.
func (s *authzService) getUserRoles(ctx context.Context, userID string) ([]*ent.Role, error) {
	userRoles, err := s.client.UserRole.Query().
		Where(userrole.UserID(userID)).
		WithRole(func(rq *ent.RoleQuery) {
			rq.WithRolePermissions(func(rpq *ent.RolePermissionQuery) {
				rpq.WithPermission()
			})
		}).
		All(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get user roles: %w", err)
	}

	roles := make([]*ent.Role, 0, len(userRoles))
	for _, ur := range userRoles {
		if ur.Edges.Role != nil {
			roles = append(roles, ur.Edges.Role)
		}
	}

	return roles, nil
}

// authorize checks if a user has a specific permission.
func (s *authzService) authorize(ctx context.Context, userID, permCode string) (bool, string, error) {
	hasPerm, err := s.hasPermission(ctx, userID, permCode)
	if err != nil {
		return false, "", err
	}

	if !hasPerm {
		return false, fmt.Sprintf("user %s does not have permission %s", userID, permCode), nil
	}

	return true, "authorized", nil
}

// authzServerImpl implements the AuthzService gRPC server.
type authzServerImpl struct {
	authzv1.UnimplementedAuthzServiceServer
	service *authzService
	logger  *zap.Logger
}

// Authorize checks if a user has a specific permission.
func (s *authzServerImpl) Authorize(ctx context.Context, req *authzv1.AuthorizeRequest) (*authzv1.AuthorizeResponse, error) {
	authorized, message, err := s.service.authorize(ctx, req.UserId, req.Permission)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "authorization check failed: %v", err)
	}

	return &authzv1.AuthorizeResponse{
		Authorized: authorized,
		Message:    message,
	}, nil
}

// HasPermission checks if a user has a specific permission.
func (s *authzServerImpl) HasPermission(ctx context.Context, req *authzv1.HasPermissionRequest) (*authzv1.HasPermissionResponse, error) {
	hasPerm, err := s.service.hasPermission(ctx, req.UserId, req.Permission)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "permission check failed: %v", err)
	}

	return &authzv1.HasPermissionResponse{
		HasPermission: hasPerm,
	}, nil
}

// GetUserPermissions returns all permissions for a user.
func (s *authzServerImpl) GetUserPermissions(ctx context.Context, req *authzv1.GetUserPermissionsRequest) (*authzv1.GetUserPermissionsResponse, error) {
	permissions, err := s.service.getUserPermissions(ctx, req.UserId)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user permissions: %v", err)
	}

	protoPerms := make([]*authzv1.Permission, 0, len(permissions))
	for _, perm := range permissions {
		protoPerms = append(protoPerms, &authzv1.Permission{
			Id:          perm.ID,
			Code:        perm.Name, // Permission.Name is the code (e.g., "blog.post.create")
			Name:        perm.Name,
			Description: "", // Permission schema doesn't have description field
		})
	}

	return &authzv1.GetUserPermissionsResponse{
		Permissions: protoPerms,
	}, nil
}

// GetUserRoles returns all roles for a user.
func (s *authzServerImpl) GetUserRoles(ctx context.Context, req *authzv1.GetUserRolesRequest) (*authzv1.GetUserRolesResponse, error) {
	roles, err := s.service.getUserRoles(ctx, req.UserId)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user roles: %v", err)
	}

	protoRoles := make([]*authzv1.Role, 0, len(roles))
	for _, role := range roles {
		// Get permission codes for this role
		permCodes := make([]string, 0)
		if role.Edges.RolePermissions != nil {
			for _, rp := range role.Edges.RolePermissions {
				if rp.Edges.Permission != nil {
					permCodes = append(permCodes, rp.Edges.Permission.Name)
				}
			}
		}

		protoRoles = append(protoRoles, &authzv1.Role{
			Id:          role.ID,
			Name:        role.Name,
			Description: role.Description,
			Permissions: permCodes,
		})
	}

	return &authzv1.GetUserRolesResponse{
		Roles: protoRoles,
	}, nil
}

// provideAuthzService creates the authz service and gRPC server.
func provideAuthzService() fx.Option {
	return fx.Options(
		// Authz service
		fx.Provide(func(client *ent.Client, log logger.Logger) (*authzService, error) {
			return &authzService{
				client: client,
				logger: log,
			}, nil
		}),

		// gRPC server implementation
		fx.Provide(func(authzService *authzService, log logger.Logger) (*authzServerImpl, error) {
			zapLogger, _ := zap.NewProduction()
			return &authzServerImpl{
				service: authzService,
				logger:  zapLogger,
			}, nil
		}),

		// gRPC server wrapper
		fx.Provide(func(
			serverImpl *authzServerImpl,
			cfg config.ConfigProvider,
			log logger.Logger,
		) (*grpcServerWrapper, error) {
			port := cfg.GetInt("services.authz.port")
			if port == 0 {
				port = 8083
			}

			addr := fmt.Sprintf("0.0.0.0:%d", port)
			listener, err := net.Listen("tcp", addr)
			if err != nil {
				return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
			}

			grpcServer := grpc.NewServer()
			authzv1.RegisterAuthzServiceServer(grpcServer, serverImpl)

			// Register health service
			healthServer := health.NewServer()
			grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
			healthServer.SetServingStatus("authz.v1.AuthzService", grpc_health_v1.HealthCheckResponse_SERVING)

			// Register reflection for grpcurl
			reflection.Register(grpcServer)

			return &grpcServerWrapper{
				server:   grpcServer,
				listener: listener,
				port:     port,
				logger:   log,
			}, nil
		}),
	)
}
