// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0

package controller

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"time"

	"git.arvados.org/arvados.git/sdk/go/arvados"
	"git.arvados.org/arvados.git/sdk/go/arvadostest"
	"git.arvados.org/arvados.git/sdk/go/ctxlog"
	"git.arvados.org/arvados.git/sdk/go/httpserver"
	"github.com/sirupsen/logrus"
	check "gopkg.in/check.v1"
)

// Gocheck boilerplate
var _ = check.Suite(&AuthSuite{})

type AuthSuite struct {
	log logrus.FieldLogger
	// testServer and testHandler are the controller being tested,
	// "zhome".
	testServer  *httpserver.Server
	testHandler *Handler
	// remoteServer ("zzzzz") forwards requests to the Rails API
	// provided by the integration test environment.
	remoteServer *httpserver.Server
	// remoteMock ("zmock") appends each incoming request to
	// remoteMockRequests, and returns 200 with an empty JSON
	// object.
	remoteMock         *httpserver.Server
	remoteMockRequests []http.Request

	fakeProvider *arvadostest.OIDCProvider
}

func (s *AuthSuite) SetUpTest(c *check.C) {
	s.log = ctxlog.TestLogger(c)

	s.remoteServer = newServerFromIntegrationTestEnv(c)
	c.Assert(s.remoteServer.Start(), check.IsNil)

	s.remoteMock = newServerFromIntegrationTestEnv(c)
	s.remoteMock.Server.Handler = http.HandlerFunc(http.NotFound)
	c.Assert(s.remoteMock.Start(), check.IsNil)

	s.fakeProvider = arvadostest.NewOIDCProvider(c)
	s.fakeProvider.AuthEmail = "active-user@arvados.local"
	s.fakeProvider.AuthEmailVerified = true
	s.fakeProvider.AuthName = "Fake User Name"
	s.fakeProvider.ValidCode = fmt.Sprintf("abcdefgh-%d", time.Now().Unix())
	s.fakeProvider.PeopleAPIResponse = map[string]interface{}{}
	s.fakeProvider.ValidClientID = "test%client$id"
	s.fakeProvider.ValidClientSecret = "test#client/secret"

	cluster := &arvados.Cluster{
		ClusterID:       "zhome",
		PostgreSQL:      integrationTestCluster().PostgreSQL,
		SystemRootToken: arvadostest.SystemRootToken,
	}
	cluster.TLS.Insecure = true
	cluster.API.MaxItemsPerResponse = 1000
	cluster.API.MaxRequestAmplification = 4
	cluster.API.RequestTimeout = arvados.Duration(5 * time.Minute)
	arvadostest.SetServiceURL(&cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
	arvadostest.SetServiceURL(&cluster.Services.Controller, "http://localhost/")

	cluster.RemoteClusters = map[string]arvados.RemoteCluster{
		"zzzzz": {
			Host:   s.remoteServer.Addr,
			Proxy:  true,
			Scheme: "http",
		},
		"zmock": {
			Host:   s.remoteMock.Addr,
			Proxy:  true,
			Scheme: "http",
		},
		"*": {
			Scheme: "https",
		},
	}
	cluster.Login.OpenIDConnect.Enable = true
	cluster.Login.OpenIDConnect.Issuer = s.fakeProvider.Issuer.URL
	cluster.Login.OpenIDConnect.ClientID = s.fakeProvider.ValidClientID
	cluster.Login.OpenIDConnect.ClientSecret = s.fakeProvider.ValidClientSecret
	cluster.Login.OpenIDConnect.EmailClaim = "email"
	cluster.Login.OpenIDConnect.EmailVerifiedClaim = "email_verified"
	cluster.Login.OpenIDConnect.AcceptAccessToken = true
	cluster.Login.OpenIDConnect.AcceptAccessTokenScope = ""

	s.testHandler = &Handler{Cluster: cluster, BackgroundContext: ctxlog.Context(context.Background(), s.log)}
	s.testServer = newServerFromIntegrationTestEnv(c)
	s.testServer.Server.BaseContext = func(net.Listener) context.Context {
		return ctxlog.Context(context.Background(), s.log)
	}
	s.testServer.Server.Handler = httpserver.AddRequestIDs(httpserver.LogRequests(s.testHandler))
	c.Assert(s.testServer.Start(), check.IsNil)
}

func (s *AuthSuite) TestLocalOIDCAccessToken(c *check.C) {
	req := httptest.NewRequest("GET", "/arvados/v1/users/current", nil)
	req.Header.Set("Authorization", "Bearer "+s.fakeProvider.ValidAccessToken())
	rr := httptest.NewRecorder()
	s.testServer.Server.Handler.ServeHTTP(rr, req)
	resp := rr.Result()
	c.Check(resp.StatusCode, check.Equals, http.StatusOK)
	var u arvados.User
	c.Check(json.NewDecoder(resp.Body).Decode(&u), check.IsNil)
	c.Check(u.UUID, check.Equals, arvadostest.ActiveUserUUID)
	c.Check(u.OwnerUUID, check.Equals, "zzzzz-tpzed-000000000000000")

	// Request again to exercise cache.
	req = httptest.NewRequest("GET", "/arvados/v1/users/current", nil)
	req.Header.Set("Authorization", "Bearer "+s.fakeProvider.ValidAccessToken())
	rr = httptest.NewRecorder()
	s.testServer.Server.Handler.ServeHTTP(rr, req)
	resp = rr.Result()
	c.Check(resp.StatusCode, check.Equals, http.StatusOK)
}