package acme

import (
	"context"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/smallstep/assert"
)

func TestAuthorization_UpdateStatus(t *testing.T) {
	type test struct {
		az  *Authorization
		err *Error
		db  DB
	}
	tests := map[string]func(t *testing.T) test{
		"ok/already-invalid": func(t *testing.T) test {
			az := &Authorization{
				Status: StatusInvalid,
			}
			return test{
				az: az,
			}
		},
		"ok/already-valid": func(t *testing.T) test {
			az := &Authorization{
				Status: StatusInvalid,
			}
			return test{
				az: az,
			}
		},
		"fail/error-unexpected-status": func(t *testing.T) test {
			az := &Authorization{
				Status: "foo",
			}
			return test{
				az:  az,
				err: NewErrorISE("unrecognized authorization status: %s", az.Status),
			}
		},
		"ok/expired": func(t *testing.T) test {
			now := clock.Now()
			az := &Authorization{
				ID:        "azID",
				AccountID: "accID",
				Status:    StatusPending,
				ExpiresAt: now.Add(-5 * time.Minute),
			}
			return test{
				az: az,
				db: &MockDB{
					MockUpdateAuthorization: func(ctx context.Context, updaz *Authorization) error {
						assert.Equals(t, updaz.ID, az.ID)
						assert.Equals(t, updaz.AccountID, az.AccountID)
						assert.Equals(t, updaz.Status, StatusInvalid)
						assert.Equals(t, updaz.ExpiresAt, az.ExpiresAt)
						return nil
					},
				},
			}
		},
		"fail/db.UpdateAuthorization-error": func(t *testing.T) test {
			now := clock.Now()
			az := &Authorization{
				ID:        "azID",
				AccountID: "accID",
				Status:    StatusPending,
				ExpiresAt: now.Add(-5 * time.Minute),
			}
			return test{
				az: az,
				db: &MockDB{
					MockUpdateAuthorization: func(ctx context.Context, updaz *Authorization) error {
						assert.Equals(t, updaz.ID, az.ID)
						assert.Equals(t, updaz.AccountID, az.AccountID)
						assert.Equals(t, updaz.Status, StatusInvalid)
						assert.Equals(t, updaz.ExpiresAt, az.ExpiresAt)
						return errors.New("force")
					},
				},
				err: NewErrorISE("error updating authorization: force"),
			}
		},
		"ok/no-valid-challenges": func(t *testing.T) test {
			now := clock.Now()
			az := &Authorization{
				ID:        "azID",
				AccountID: "accID",
				Status:    StatusPending,
				ExpiresAt: now.Add(5 * time.Minute),
				Challenges: []*Challenge{
					{Status: StatusPending}, {Status: StatusPending}, {Status: StatusPending},
				},
			}
			return test{
				az: az,
			}
		},
		"ok/valid": func(t *testing.T) test {
			now := clock.Now()
			az := &Authorization{
				ID:        "azID",
				AccountID: "accID",
				Status:    StatusPending,
				ExpiresAt: now.Add(5 * time.Minute),
				Challenges: []*Challenge{
					{Status: StatusPending}, {Status: StatusPending}, {Status: StatusValid},
				},
			}
			return test{
				az: az,
				db: &MockDB{
					MockUpdateAuthorization: func(ctx context.Context, updaz *Authorization) error {
						assert.Equals(t, updaz.ID, az.ID)
						assert.Equals(t, updaz.AccountID, az.AccountID)
						assert.Equals(t, updaz.Status, StatusValid)
						assert.Equals(t, updaz.ExpiresAt, az.ExpiresAt)
						assert.Equals(t, updaz.Error, nil)
						return nil
					},
				},
			}
		},
	}
	for name, run := range tests {
		t.Run(name, func(t *testing.T) {
			tc := run(t)
			if err := tc.az.UpdateStatus(context.Background(), tc.db); err != nil {
				if assert.NotNil(t, tc.err) {
					switch k := err.(type) {
					case *Error:
						assert.Equals(t, k.Type, tc.err.Type)
						assert.Equals(t, k.Detail, tc.err.Detail)
						assert.Equals(t, k.Status, tc.err.Status)
						assert.Equals(t, k.Err.Error(), tc.err.Err.Error())
						assert.Equals(t, k.Detail, tc.err.Detail)
					default:
						assert.FatalError(t, errors.New("unexpected error type"))
					}
				}
			} else {
				assert.Nil(t, tc.err)
			}
		})

	}
}