// This file is part of taldir, the Taler Directory implementation.
// Copyright (C) 2022 Martin Schanzenbach
//
// Taldir is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// Taldir is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL3.0-or-later

package main_test

import (
	"bytes"
	"database/sql"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"

	"github.com/schanzen/taler-go/pkg/merchant"
	talerutil "github.com/schanzen/taler-go/pkg/util"
	_ "taler.net/taldir/cmd/taldir-server"
	"taler.net/taldir/internal/util"
	taldir "taler.net/taldir/pkg/taldir"
)

var t taldir.Taldir

// Note: This duration will be rounded down to 20 Months (= 51840000000000)
var validRegisterRequest = []byte(`
  {
    "alias": "abc@test",
    "target_uri": "myinbox@xyz",
    "duration": 53135000000000
  }
`)

var validRegisterRequestUnmodified = []byte(`
  {
    "alias": "abc@test",
    "target_uri": "myinbox@xyz",
    "duration": 0
  }
`)

var newOrderMockResponse = `
  {
    "order_id": "testOrder1234",
    "taler_pay_uri": "payto://ladida"
  }
`

var newOrderStatusUnpaidMockResponse = `
  {
    "order_status": "unpaid",
    "taler_pay_uri": "payto://somedude"
  }
`

const merchantConfigResponse = `{
  "currency": "KUDOS",
  "currencies": {
    "KUDOS": {
      "name": "Kudos (Taler Demonstrator)",
      "currency": "KUDOS",
      "num_fractional_input_digits": 2,
      "num_fractional_normal_digits": 2,
      "num_fractional_trailing_zero_digits": 2,
      "alt_unit_names": {
        "0": "ク"
      }
    }
  },
  "exchanges": [
    {
      "master_pub": "F80MFRG8HVH6R9CQ47KRFQSJP3T6DBJ4K1D9B703RJY3Z39TBMJ0",
      "currency": "KUDOS",
      "base_url": "https://exchange.demo.taler.net/"
    }
  ],
  "implementation": "urn:net:taler:specs:taler-merchant:c-reference",
  "name": "taler-merchant",
  "version": "18:0:15"
}`

func TestMain(m *testing.M) {
	cfg, err := talerutil.LoadConfiguration("testdata/taldir-test.conf")
	if err != nil {
		log.Fatalf("Failed to read config: %v", err)
	}
	psqlconn := cfg.GetString("directory-pq", "connection_string", "postgres:///taler-directory")
	segments := strings.Split(strings.Split(psqlconn, "?")[0], "/")
	dbName := segments[len(segments)-1]

	db, err := sql.Open("postgres", psqlconn)
	if err != nil {
		log.Panic(err)
	}
	defer db.Close()
	err = talerutil.DBInit(db, "../..", dbName, "taler-directory")
	if err != nil {
		log.Fatalf("Failed to apply versioning or patches: %v", err)
	}
	merchServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/config" {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(merchantConfigResponse))
			return
		}
		if !strings.HasPrefix(r.URL.Path, "/private/orders") {
			log.Printf("Expected to request '/private/orders', got: %s\n", r.URL.Path)
			return
		}
		if r.Method == http.MethodPost {
			jsonResp := fmt.Sprintf("{\"order_id\":\"%s\"}", "uniqueOrderId")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(jsonResp))
		} else {
			if r.Header.Get("PaidIndicator") == "yes" {
				jsonResp := "{\"order_status\":\"paid\"}"
				w.WriteHeader(http.StatusOK)
				w.Write([]byte(jsonResp))
			} else {
				jsonResp := "{\"order_status\":\"unpaid\", \"taler_pay_uri\": \"somepaytouri\"}"
				w.WriteHeader(http.StatusOK)
				w.Write([]byte(jsonResp))
			}
		}
	}))
	defer merchServer.Close()
	merch := merchant.NewMerchant(merchServer.URL, "supersecret")
	t.Initialize(taldir.TaldirConfig{
		Ini:      cfg,
		Version:  "testing",
		Datahome: "./testdata",
		Db:       db,
		Merchant: merch,
		Loglevel: taldir.LogDebug,
	})
	log.Printf("have %d validators", len(t.Validators))
	log.Print(t.Validators)
	code := m.Run()
	t.ClearDatabase()
	os.Exit(code)
}

func getHAlias(alias string) string {
	ha := taldir.HashAlias("test", alias)
	return util.Base32CrockfordEncode(ha)
}

func TestNoEntry(s *testing.T) {
	t.ClearDatabase()

	hAlias := getHAlias("jdoe@example.com")
	req, _ := http.NewRequest("GET", "/"+hAlias, nil)
	response := executeRequest(req)

	if http.StatusNotFound != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusNotFound, response.Code)
	}
}

func executeRequest(req *http.Request) *httptest.ResponseRecorder {
	rr := httptest.NewRecorder()
	t.Router.ServeHTTP(rr, req)
	return rr
}

func TestRegisterRequest(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d, with %s\n", http.StatusAccepted, response.Code, response.Body)
	}
	file, err := os.Open("validation_code")
	if err != nil {
		s.Errorf("No validation code file found!\n")
	}
	code, err := io.ReadAll(file)
	if err != nil {
		s.Errorf("Error reading validation code file contents!\n")
	}
	hAlias := getHAlias("abc@test")
	trimCode := strings.Trim(string(code), " \r\n")
	solution := util.GenerateSolution("myinbox@xyz", trimCode)
	solutionJSON := "{\"solution\": \"" + solution + "\"}"
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusNoContent != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusNoContent, response.Code)
	}
}

func TestRegisterQRPageRequest(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	req, _ = http.NewRequest("GET", "/register/NonSenseAddr/NonSenseCode", nil)
	response = executeRequest(req)
	if http.StatusNotFound != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusNotFound, response.Code)
	}

	file, err := os.Open("validation_code")
	if err != nil {
		s.Errorf("No validation code file found!\n")
	}
	code, err := io.ReadAll(file)
	if err != nil {
		s.Errorf("Error reading validation code file contents!\n")
	}
	hAlias := getHAlias("abc@test")
	trimCode := strings.Trim(string(code), " \r\n")
	req, _ = http.NewRequest("GET", "/register/"+hAlias+"/"+trimCode+"?alias="+"abc@test", nil)
	response = executeRequest(req)
	if http.StatusOK != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusOK, response.Code)
	}
}

func TestReRegisterRequest(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	file, err := os.Open("validation_code")
	if err != nil {
		s.Errorf("No validation code file found!\n")
	}
	code, err := io.ReadAll(file)
	if err != nil {
		s.Errorf("Error reading validation code file contents!\n")
	}
	hAlias := getHAlias("abc@test")
	trimCode := strings.Trim(string(code), " \r\n")
	solution := util.GenerateSolution("myinbox@xyz", trimCode)
	solutionJSON := "{\"solution\": \"" + solution + "\"}"
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusNoContent != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusNoContent, response.Code)
	}
	req, _ = http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequestUnmodified))
	response = executeRequest(req)

	if http.StatusOK != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusOK, response.Code)
	}

}

func TestReRegisterRequestTooMany(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	req, _ = http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response = executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	req, _ = http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response = executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	req, _ = http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response = executeRequest(req)

	if http.StatusTooManyRequests != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusTooManyRequests, response.Code)
	}

}

func TestSolutionRequestTooMany(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	hAlias := getHAlias("abc@test")
	solution := util.GenerateSolution("myinbox@xyz", "wrongSolution")
	solutionJSON := "{\"solution\": \"" + solution + "\"}"
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusForbidden != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, response.Code)
	}
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusForbidden != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, response.Code)
	}
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusForbidden != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, response.Code)
	}
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusTooManyRequests != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusTooManyRequests, response.Code)
	}

}

func TestRegisterRequestWrongTargetUri(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/test", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusAccepted != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusAccepted, response.Code)
	}
	file, err := os.Open("validation_code")
	if err != nil {
		s.Errorf("No validation code file found!\n")
	}
	code, err := io.ReadAll(file)
	if err != nil {
		s.Errorf("Error reading validation code file contents!\n")
	}
	hAlias := getHAlias("abc@test")
	trimCode := strings.Trim(string(code), " \r\n")
	solution := util.GenerateSolution("myinox@xyz", trimCode)
	solutionJSON := "{\"solution\": \"" + solution + "\"}"
	req, _ = http.NewRequest("POST", "/"+hAlias, bytes.NewBuffer([]byte(solutionJSON)))
	response = executeRequest(req)
	if http.StatusForbidden != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusForbidden, response.Code)
	}
}

func TestUnsupportedAliasType(s *testing.T) {
	t.ClearDatabase()

	req, _ := http.NewRequest("POST", "/register/email", bytes.NewBuffer(validRegisterRequest))
	response := executeRequest(req)

	if http.StatusNotFound != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusNotFound, response.Code)
	}
}

func TestPaymentRequiredMethod(s *testing.T) {
	t.ClearDatabase()
	t.MonthlyFee, _ = talerutil.ParseAmount("KUDOS:5")
	req, _ := http.NewRequest("POST", "/register/test-cost", bytes.NewBuffer(validRegisterRequest))

	response := executeRequest(req)
	t.MonthlyFee, _ = talerutil.ParseAmount("KUDOS:0")
	if http.StatusPaymentRequired != response.Code {
		s.Errorf("Expected response code %d. Got %d\n", http.StatusPaymentRequired, response.Code)
	}
}
