/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package smoke

import (
	"context"
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"fotff/tester"
	"fotff/utils"
	"github.com/sirupsen/logrus"
	"math/rand"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

type Tester struct {
	Py         string `key:"py"`
	Config     string `key:"config"`
	AnswerPath string `key:"answer_path"`
	SavePath   string `key:"save_path"`
	ToolsPath  string `key:"tools_path"`
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func NewTester() tester.Tester {
	ret := &Tester{}
	utils.ParseFromConfigFile("smoke", ret)
	return ret
}

func (t *Tester) TaskName() string {
	return "smoke_test"
}

func (t *Tester) Prepare(pkgDir string, device string, ctx context.Context) error {
	return nil
}

func (t *Tester) DoTestTask(deviceSN string, ctx context.Context) (ret []tester.Result, err error) {
	reportDir := fmt.Sprintf("%X", md5.Sum([]byte(fmt.Sprintf("%d", rand.Int()))))
	if err := os.MkdirAll(filepath.Join(t.SavePath, reportDir), 0755); err != nil {
		return nil, err
	}
	args := []string{t.Py, "--config", t.Config, "--answer_path", t.AnswerPath, "--save_path", filepath.Join(t.SavePath, reportDir), "--tools_path", t.ToolsPath}
	if deviceSN != "" {
		args = append(args, "--device_num", deviceSN)
	}
	if err := utils.ExecContext(ctx, "python", args...); err != nil {
		if errors.Is(err, context.Canceled) {
			return nil, err
		}
		logrus.Errorf("do test suite fail: %v", err)
		return nil, err
	}
	return t.readReport(reportDir)
}

func (t *Tester) DoTestCase(deviceSN, testCase string, ctx context.Context) (ret tester.Result, err error) {
	reportDir := fmt.Sprintf("%X", md5.Sum([]byte(fmt.Sprintf("%d", rand.Int()))))
	if err := os.MkdirAll(filepath.Join(t.SavePath, reportDir), 0755); err != nil {
		return ret, err
	}
	args := []string{t.Py, "--config", t.Config, "--answer_path", t.AnswerPath, "--save_path", filepath.Join(t.SavePath, reportDir), "--tools_path", t.ToolsPath, "--test_num", testCase}
	if deviceSN != "" {
		args = append(args, "--device_num", deviceSN)
	}
	if err := utils.ExecContext(ctx, "python", args...); err != nil {
		if errors.Is(err, context.Canceled) {
			return ret, err
		}
		logrus.Errorf("do test case %s fail: %v", testCase, err)
		return ret, err
	}
	r, err := t.readReport(reportDir)
	if len(r) == 0 {
		return ret, fmt.Errorf("read latest report err, no result found")
	}
	if r[0].TestCaseName != testCase {
		return ret, fmt.Errorf("read latest report err, no matched result found")
	}
	logrus.Infof("do testcase %s at %s done, result is %s", r[0].TestCaseName, deviceSN, r[0].Status)
	return r[0], nil
}

func (t *Tester) DoTestCases(deviceSN string, testcases []string, ctx context.Context) (ret []tester.Result, err error) {
	reportDir := fmt.Sprintf("%X", md5.Sum([]byte(fmt.Sprintf("%d", rand.Int()))))
	if err := os.MkdirAll(filepath.Join(t.SavePath, reportDir), 0755); err != nil {
		return nil, err
	}
	args := []string{t.Py, "--config", t.Config, "--answer_path", t.AnswerPath, "--save_path", filepath.Join(t.SavePath, reportDir), "--tools_path", t.ToolsPath, "--test_num", strings.Join(testcases, " ")}
	if deviceSN != "" {
		args = append(args, "--device_num", deviceSN)
	}
	if err := utils.ExecContext(ctx, "python", args...); err != nil {
		if errors.Is(err, context.Canceled) {
			return ret, err
		}
		logrus.Errorf("do test cases %v fail: %v", testcases, err)
		return ret, err
	}
	return t.readReport(reportDir)
}

func (t *Tester) readReport(reportDir string) (ret []tester.Result, err error) {
	data, err := os.ReadFile(filepath.Join(t.SavePath, reportDir, "result.json"))
	if err != nil {
		logrus.Errorf("read report json fail: %v", err)
		return nil, err
	}
	var result []struct {
		TestCaseName int    `json:"test_case_name"`
		Status       string `json:"status"`
	}
	err = json.Unmarshal(data, &result)
	if err != nil {
		logrus.Errorf("unmarshal report xml fail: %v", err)
		return nil, err
	}
	for _, r := range result {
		if r.Status == "pass" {
			ret = append(ret, tester.Result{TestCaseName: strconv.Itoa(r.TestCaseName), Status: tester.ResultPass})
		} else {
			ret = append(ret, tester.Result{TestCaseName: strconv.Itoa(r.TestCaseName), Status: tester.ResultFail})
		}
	}
	return ret, err
}