package cmd

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/spf13/viper"
)

type APITarget struct {
	url            string
	method         string
	timeout        time.Duration
	ssl_skipverify bool
	content_type   string
	body           string
}

type APIResult struct {
	http_code    int
	duration     time.Duration
	content_type string
	body         string
}

// var http_client = &http.Client{
// 	Timeout: 5000 * time.Millisecond,
// }

// Call API
func CallAPI(target APITarget) (APIResult, error) {

	var result APIResult

	// Use method from target, default to GET if not specified
	method := target.method
	if method == "" {
		method = http.MethodGet
	}

	timeout := target.timeout
	if timeout <= 0 {
		timeout = time.Duration(viper.GetInt("timeout")) * time.Millisecond
	}

	// CLIENT/CONTEXT/TRANSPORT

	// ctx, cancel := context.WithTimeout(context.Background(),
	// 	timeout)
	// defer cancel()

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: target.ssl_skipverify},
	}

	http_client := &http.Client{
		Transport: transport,
		Timeout:   timeout,
	}

	// Create request body reader if body is provided
	var bodyReader io.Reader
	if target.body != "" {
		bodyReader = strings.NewReader(target.body)
	}

	// Create Request
	//req, err := http.NewRequestWithContext(ctx,
	req, err := http.NewRequest(
		method,
		target.url,
		bodyReader)
	if err != nil {
		return result, errors.New("invalid server parameters")
	}

	var header = viper.GetString("client_id") + " " + viper.GetString("client_secret")
	req.Header.Add("X-Cavaliba-Key", header)

	// Set Content-Type header, default to application/json if not specified
	contentType := target.content_type
	if contentType == "" {
		contentType = "application/json"
	}
	req.Header.Add("Content-Type", contentType)

	start := time.Now()

	// PERFORM REQUEST
	resp, err := http_client.Do(req)
	if err != nil {
		return result, errors.New("no answer from server")
	}
	defer resp.Body.Close()

	result.duration = time.Since(start)
	result.http_code = resp.StatusCode
	result.content_type = resp.Header.Get("Content-Type")

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		//log.Println(err)
		return result, errors.New("can't read server answer")
	}

	result.body = string(bodyBytes)

	if resp.StatusCode == 401 {
		return result, fmt.Errorf("denied")
	}
	if resp.StatusCode == 404 {
		return result, fmt.Errorf("not found")
	}
	if resp.StatusCode == 405 {
		return result, fmt.Errorf("method not allowed")
	}
	if resp.StatusCode != 200 {
		return result, fmt.Errorf("http error from server")
	}

	return result, nil
}

// PrintVerboseTarget displays API call details when verbose flag is enabled
func PrintVerboseTarget(target APITarget) {
	if verbose {
		method := target.method
		if method == "" {
			method = "GET"
		}
		contentType := target.content_type
		if contentType == "" {
			contentType = "application/json"
		}
		fmt.Println("API Request")
		fmt.Println("-----------")
		fmt.Println("Method:         ", method)
		fmt.Println("API:            ", target.url)
		fmt.Println("Content-Type:   ", contentType)
		fmt.Println("")
	}
}

// PrintVerboseResult displays API call details when verbose flag is enabled
func PrintVerboseResult(result APIResult) {
	if verbose {
		fmt.Println("API Response")
		fmt.Println("------------")
		fmt.Println("status:         ", result.http_code)
		fmt.Println("duration:       ", result.duration)
		fmt.Println("")
		fmt.Println("DATA")
		fmt.Println("----")
	}
}

// PrintOutput writes s to outfile (append) if --outfile is set, otherwise to stdout.
func PrintOutput(s string) {
	if outfile != "" {
		f, err := os.OpenFile(outfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "outfile error: %s\n", err)
			fmt.Println(s)
			return
		}
		defer f.Close()
		fmt.Fprintln(f, s)
		return
	}
	fmt.Println(s)
}

func PrintError(result APIResult, err error) {
	if verbose {
		fmt.Println("API Response")
		fmt.Println("------------")
		fmt.Println("status:         ", result.http_code)
		fmt.Println("error:          ", err)
		fmt.Println("duration:       ", result.duration)
		fmt.Println("")
		fmt.Println("DATA")
		fmt.Println("----")
	}
	PrintOutput(result.body)
}

// AppendGlobalOptions appends query parameters to the URL based on global flags
func AppendGlobalOptions(target *APITarget) error {
	separator := "?"

	if expand {
		target.url = target.url + separator + "expand=true"
		separator = "&"
	}

	if search != "" {
		target.url = target.url + separator + "search=" + search
		separator = "&"
	}

	if schema != "" {
		target.url = target.url + separator + "schema=" + schema
		separator = "&"
	}

	if page > 0 {
		target.url = target.url + separator + fmt.Sprintf("page=%d", page)
		separator = "&"
	}

	if size > 0 {
		target.url = target.url + separator + fmt.Sprintf("size=%d", size)
		separator = "&"
	}

	if pipeline != "" {
		target.url = target.url + separator + "pipeline=" + pipeline
		separator = "&"
	}

	if delimiter != "," {
		target.url = target.url + separator + "delimiter=" + delimiter
		separator = "&"
	}

	if refs != "" {
		target.url = target.url + separator + "refs=" + refs
		separator = "&"
	}

	if rev > 0 {
		target.url = target.url + separator + fmt.Sprintf("rev=%d", rev)
		separator = "&"
	}

	if format == "json" {
		target.url = target.url + separator + "o=json"
		separator = "&"
	} else if format == "yaml" {
		target.url = target.url + separator + "o=yaml"
		separator = "&"
	} else if format == "txt" {
		target.url = target.url + separator + "o=txt"
		separator = "&"
	} else {
		return fmt.Errorf("invalid format: %s (valid formats: json, yaml, txt)", format)
	}

	return nil
}
