// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// apps.go contains functions for accessing data about applications installed
// on a GitHub organization.

package scrape

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/google/go-github/v75/github"
)

// AppRestrictionsEnabled returns whether the specified organization has
// restricted third-party application access.
func (c *Client) AppRestrictionsEnabled(org string) (bool, error) {
	doc, err := c.get("/organizations/%v/settings/oauth_application_policy", org)
	if err != nil {
		return false, err
	}

	s := doc.Find(".oauth-application-allowlist svg").First()
	if s.Length() == 0 {
		return false, errors.New("unable to find expected markup")
	}

	if s.HasClass("octicon-check") {
		return true, nil
	}
	if s.HasClass("octicon-alert") {
		return false, nil
	}

	return false, errors.New("unable to find expected markup")
}

// ListOAuthApps lists the reviewed OAuth Applications for the
// specified organization (whether approved or denied).
func (c *Client) ListOAuthApps(org string) ([]*OAuthApp, error) {
	doc, err := c.get("/organizations/%v/settings/oauth_application_policy", org)
	if err != nil {
		return nil, err
	}

	var apps []*OAuthApp
	doc.Find(".oauth-application-allowlist ul > li").Each(func(_ int, s *goquery.Selection) {
		var app OAuthApp
		app.Name = s.Find(".request-info strong").First().Text()
		app.Description = s.Find(".request-info .application-description").Text()

		if editURL, ok := s.Find(".request-indicator a.edit-link").Attr("href"); ok {
			app.ID = intFromLastPathSegment(editURL)
		}

		if r := s.Find(".request-indicator .requestor"); r.Length() > 0 {
			app.State = OAuthAppRequested
			app.RequestedBy = r.Text()
			if editURL, ok := s.Find(".request-indicator a").Last().Attr("href"); ok {
				app.ID = intFromLastPathSegment(editURL)
			}
		} else if r := s.Find(".request-indicator .approved-request"); r.Length() > 0 {
			app.State = OAuthAppApproved
		} else if r := s.Find(".request-indicator .denied-request"); r.Length() > 0 {
			app.State = OAuthAppDenied
		}
		apps = append(apps, &app)
	})

	return apps, nil
}

func intFromLastPathSegment(s string) int {
	seg := strings.Split(s, "/")
	if len(seg) > 0 {
		i, _ := strconv.Atoi(seg[len(seg)-1])
		return i
	}
	return 0
}

// OAuthAppReviewState indicates the current state of a requested OAuth Application.
type OAuthAppReviewState int

const (
	// OAuthAppRequested indicates access has been requested, but not reviewed.
	OAuthAppRequested OAuthAppReviewState = iota + 1
	// OAuthAppApproved indicates access has been approved.
	OAuthAppApproved
	// OAuthAppDenied indicates access has been denied.
	OAuthAppDenied
)

// OAuthApp represents an OAuth application that has been reviewed for access to organization data.
type OAuthApp struct {
	ID          int
	Name        string
	Description string
	State       OAuthAppReviewState
	RequestedBy string
}

// AppManifest represents a GitHub App manifest, used for preconfiguring
// GitHub App configuration.
// c.f. https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest
type AppManifest struct {
	// The name of the GitHub App.
	Name *string `json:"name,omitempty"`
	// Required. The homepage of your GitHub App.
	URL *string `json:"url,omitempty"`
	// The full URL(s) of the endpoint(s) to authenticate users via the GitHub App (Max: 10).
	CallbackURLs []string `json:"callback_urls,omitempty"`
	// Required. The configuration of the GitHub App's webhook.
	HookAttributes map[string]string `json:"hook_attributes,omitempty"`
	// The full URL to redirect to after the person installs the GitHub App.
	RedirectURL *string `json:"redirect_url,omitempty"`
	// A description of the GitHub App.
	Description *string `json:"description,omitempty"`
	// Set to true when your GitHub App is available to the public or false when
	// it is only accessible to the owner of the app.
	Public *bool `json:"public,omitempty"`
	// The list of events the GitHub App subscribes to.
	DefaultEvents []string `json:"default_events,omitempty"`
	// The set of permissions needed by the GitHub App.
	DefaultPermissions *github.InstallationPermissions `json:"default_permissions,omitempty"`
}

// CreateApp creates a new GitHub App with the given manifest configuration.
// orgName is optional, and if provided, the App will be created within the specified organization.
func (c *Client) CreateApp(m *AppManifest, orgName string) (*http.Response, error) {
	url := "/settings/apps/new"

	if orgName != "" {
		url = fmt.Sprintf("/organizations/%v/settings/apps/new", orgName)
	}

	u, err := c.baseURL.Parse(url)
	if err != nil {
		return nil, err
	}

	body, err := json.Marshal(map[string]*AppManifest{"manifest": m})
	if err != nil {
		return nil, err
	}

	return c.Client.Post(u.String(), "json", bytes.NewReader(body))
}
