package analyzers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/fatih/color"
	"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
	"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
	"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)

type (
	Analyzer interface {
		Type() analyzerpb.AnalyzerType
		Analyze(ctx context.Context, credentialInfo map[string]string) (*AnalyzerResult, error)
	}

	// AnalyzerResult is the output of analysis.
	AnalyzerResult struct {
		AnalyzerType       analyzerpb.AnalyzerType
		Bindings           []Binding
		UnboundedResources []Resource
		Metadata           map[string]any
	}

	Resource struct {
		Name               string
		FullyQualifiedName string
		Type               string
		Metadata           map[string]any
		Parent             *Resource
	}

	Permission struct {
		Value  string
		Parent *Permission
	}

	Binding struct {
		Resource   Resource
		Permission Permission
	}
)

type PermissionType string

const (
	READ       PermissionType = "Read"
	WRITE      PermissionType = "Write"
	READ_WRITE PermissionType = "Read & Write"
	NONE       PermissionType = "None"
	ERROR      PermissionType = "Error"

	FullAccess string = "full_access"
)

// Sorted list of all available analyzers. Used for valid sub-commands and TUI
// selection. TODO: Change slice type to Analyzer interface when all available
// analyzers implement it.
var AvailableAnalyzers = []string{
	"Airbrake",
	"Asana",
	"Bitbucket",
	"GitHub",
	"GitLab",
	"HuggingFace",
	"Mailchimp",
	"Mailgun",
	"MySQL",
	"OpenAI",
	"Opsgenie",
	"Postgres",
	"Postman",
	"Sendgrid",
	"Shopify",
	"Slack",
	"Sourcegraph",
	"Square",
	"Stripe",
	"Twilio",
}

type PermissionStatus struct {
	Value   bool
	IsError bool
}

type HttpStatusTest struct {
	URL     string
	Method  string
	Payload map[string]interface{}
	Params  map[string]string
	Valid   []int
	Invalid []int
	Type    PermissionType
	Status  PermissionStatus
	Risk    string
}

func (h *HttpStatusTest) RunTest(headers map[string]string) error {
	// If body data, marshal to JSON
	var data io.Reader
	if h.Payload != nil {
		jsonData, err := json.Marshal(h.Payload)
		if err != nil {
			return err
		}
		data = bytes.NewBuffer(jsonData)
	}

	// Create new HTTP request
	client := &http.Client{}
	req, err := http.NewRequest(h.Method, h.URL, data)
	if err != nil {
		return err
	}

	// Add custom headers if provided
	for key, value := range headers {
		req.Header.Set(key, value)
	}

	// Execute HTTP Request
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Check response status code
	switch {
	case StatusContains(resp.StatusCode, h.Valid):
		h.Status.Value = true
	case StatusContains(resp.StatusCode, h.Invalid):
		h.Status.Value = false
	default:
		h.Status.IsError = true
	}
	return nil
}

type Scope struct {
	Name  string
	Tests []interface{}
}

func StatusContains(status int, vals []int) bool {
	for _, v := range vals {
		if status == v {
			return true
		}
	}
	return false
}

func GetWriterFromStatus(status PermissionType) func(a ...interface{}) string {
	switch status {
	case READ:
		return color.New(color.FgYellow).SprintFunc()
	case WRITE:
		return color.New(color.FgGreen).SprintFunc()
	case READ_WRITE:
		return color.New(color.FgGreen).SprintFunc()
	case NONE:
		return color.New().SprintFunc()
	case ERROR:
		return color.New(color.FgRed).SprintFunc()
	default:
		return color.New().SprintFunc()
	}
}

var GreenWriter = color.New(color.FgGreen).SprintFunc()
var YellowWriter = color.New(color.FgYellow).SprintFunc()
var RedWriter = color.New(color.FgRed).SprintFunc()
var DefaultWriter = color.New().SprintFunc()

type AnalyzeClient struct {
	http.Client
	LoggingEnabled bool
	LogFile        string
}

func CreateLogFileName(baseName string) string {
	// Get the current time
	currentTime := time.Now()

	// Format the time as "2024_06_30_07_15_30"
	timeString := currentTime.Format("2006_01_02_15_04_05")

	// Create the log file name
	logFileName := fmt.Sprintf("%s_%s.log", timeString, baseName)
	return logFileName
}

func NewAnalyzeClient(cfg *config.Config) *http.Client {
	if cfg == nil || !cfg.LoggingEnabled {
		return &http.Client{}
	}
	return &http.Client{
		Transport: LoggingRoundTripper{
			parent:  http.DefaultTransport,
			logFile: cfg.LogFile,
		},
	}
}

type LoggingRoundTripper struct {
	parent http.RoundTripper
	// TODO: io.Writer
	logFile string
}

func (r LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	startTime := time.Now()

	resp, err := r.parent.RoundTrip(req)
	if err != nil {
		return resp, err
	}

	// TODO: JSON
	logEntry := fmt.Sprintf("Date: %s, Method: %s, Path: %s, Status: %d\n", startTime.Format(time.RFC3339), req.Method, req.URL.Path, resp.StatusCode)

	// Open log file in append mode.
	file, err := os.OpenFile(r.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return resp, fmt.Errorf("failed to open log file: %w", err)
	}
	defer file.Close()

	// Write log entry to file.
	if _, err := file.WriteString(logEntry); err != nil {
		return resp, fmt.Errorf("failed to write log entry to file: %w", err)
	}

	return resp, nil
}

// BindAllPermissions creates a Binding for each permission to the given
// resource.
func BindAllPermissions(r Resource, perms ...Permission) []Binding {
	bindings := make([]Binding, len(perms))
	for i, perm := range perms {
		bindings[i] = Binding{
			Resource:   r,
			Permission: perm,
		}
	}
	return bindings
}
