package web

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws/arn"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"

	"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
)

// classifyPolicies distributes queue policies across five bins:
//   - wild: policies describing a DLQ with an allowAll policy
//   - closed: policies describing a DLQ with an denyAll policy
//   - byQueue: policies describing a DLQ with an byQueue policy
//   - sources: policies describing a queue with a DLQ
//   - notSources: policies describing a queue without a DLQ
//
// Note that dead-letter queues may also be source queues, for multi-level processing, and vice-versa.
//
// For easier reading, DLQs are not listed as part of the non-source list.
func classifyPolicies(policies map[string]*redriver.QueueRedrivePolicies) (byQueue, wild, closed, sources map[string]redriver.QueueRedrivePolicies, notSources []string) {
	DLQs := make(map[string]redriver.QueueRedrivePolicies)
	sources = make(map[string]redriver.QueueRedrivePolicies)
	wild = make(map[string]redriver.QueueRedrivePolicies)
	closed = make(map[string]redriver.QueueRedrivePolicies)
	byQueue = make(map[string]redriver.QueueRedrivePolicies)
	nsTemp := make([]string, 0, len(notSources))

	for qURL, qrp := range policies {
		if qrp.QueueInfoAttributesRedriveAllowPolicy != nil {
			DLQs[qURL] = *qrp
		}
		if qrp.QueueInfoAttributesRedrivePolicy != nil {
			sources[qURL] = *qrp
		} else {
			nsTemp = append(nsTemp, qURL)
		}
	}
	for _, qURL := range nsTemp {
		if _, ok := DLQs[qURL]; !ok {
			notSources = append(notSources, qURL)
		}
	}
	for qURL, qrp := range DLQs {
		rap := qrp.QueueInfoAttributesRedriveAllowPolicy
		switch rap.RedrivePermission {
		case "allowAll":
			wild[qURL] = qrp
		case "denyAll":
			closed[qURL] = qrp
		case "byQueue":
			byQueue[qURL] = qrp
		}
	}

	return
}

func policiesByURL(c *gin.Context, qURLs []string, rd redriver.Redriver, ctx context.Context) map[string]*redriver.QueueRedrivePolicies {
	qMap := make(map[string]*redriver.QueueRedrivePolicies, len(qURLs))
	for _, qURL := range qURLs {
		name, err := redriver.NameFromURL(qURL)
		if err != nil {
			log.Println(http.StatusInternalServerError)
			c.HTML(http.StatusInternalServerError, "500", nil)
		}
		qrp, err := rd.GetRedrivePolicies(ctx, name)
		if err != nil || qrp == nil {
			log.Println(err)
			c.HTML(http.StatusInternalServerError, "500", nil)
		}
		qMap[qURL] = qrp
	}
	return qMap
}

type link struct{ URL, Text string }

// queueRow provides templates with a representation for all kinds of table cells
type queueCell struct {
	Message string
	Link    *link
	Links   [][]link
}

type QueueRow [2]queueCell

func makeHomeHandler(rd redriver.Redriver, prefix string) gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		sess := sessions.Default(c)
		flashes := sess.Flashes()
		defer func() { _ = sess.Save() }()

		t0 := time.Now()
		qURLs, err := rd.ListQueues(ctx, prefix)
		latency := time.Since(t0)
		if err != nil {
			log.Printf("failed listing queues: %v", err)
			c.HTML(http.StatusInternalServerError, "500", nil)
			return
		}
		policies := policiesByURL(c, qURLs, rd, ctx)
		byQueue, wild, closed, sources, notSources := classifyPolicies(policies)
		// log.Printf("DLQs: %#v\nSRCs: %#v\nNoSRCs: %#v\n", maps.Keys(DLQs), maps.Keys(SRCs), NoSRCs)
		rows := make([]QueueRow, 0, len(byQueue)+3) // wild, closed, notSources: sources appear within a byQueue row

		rows, err = prepareSingleLeft(rows, wild, "All source queues allowed")
		if err != nil {
			log.Printf("failed preparing wild DLQs: %v", err)
			c.HTML(http.StatusInternalServerError, "500", nil)
			return
		}
		rows, err = prepareSingleLeft(rows, closed, "No source queue allowed")
		if err != nil {
			log.Printf("failed preparing closed DLQs: %v", err)
			c.HTML(http.StatusInternalServerError, "500", nil)
			return
		}
		rows, err = prepareSingleRight(rows, notSources, "No associated DLQ")
		if err != nil {
			log.Printf("failed converting isolated queues: %v", err)
			c.HTML(http.StatusInternalServerError, "500", nil)
			return
		}
		rows, err = prepareBindings(rows, byQueue, sources)
		if err != nil {
			log.Printf("failed preparing queue bindings: %v", err)
			c.HTML(http.StatusInternalServerError, "500", nil)
			return
		}
		c.HTML(http.StatusOK, "home", gin.H{
			"flashes": flashes,
			"latency": latency,
			"prefix":  prefix,
			"rows":    rows,
		})

	}
}

func prepareSingleLeft(rows []QueueRow, list map[string]redriver.QueueRedrivePolicies, msg string) ([]QueueRow, error) {
	if len(list) > 0 {
		row := QueueRow{
			{Message: msg},
			{Links: make([][]link, 1)},
		}
		row[1].Links[0] = make([]link, 0, len(list))
		for qURL := range list {
			name, err := redriver.NameFromURL(qURL)
			if err != nil {
				return nil, err
			}
			row[1].Links[0] = append(row[1].Links[0], link{URL: "/queue/" + name, Text: name})
		}
		rows = append(rows, row)
	}
	return rows, nil
}

func prepareSingleRight(rows []QueueRow, list []string, msg string) ([]QueueRow, error) {
	if len(list) > 0 {
		row := QueueRow{
			{Links: make([][]link, 1)},
			{Message: msg},
		}
		row[0].Links[0] = make([]link, 0, len(list))
		for _, qURL := range list {
			name, err := redriver.NameFromURL(qURL)
			if err != nil {
				return nil, err
			}
			row[0].Links[0] = append(row[0].Links[0], link{URL: "/queue/" + name, Text: name})
		}
		rows = append(rows, row)
	}
	return rows, nil
}

func prepareBinding(dlqURL string, DLQrp redriver.QueueRedrivePolicies, sources map[string]redriver.QueueRedrivePolicies) (*QueueRow, error) {
	var left, right queueCell
	dlqName, err := redriver.NameFromURL(dlqURL)
	if err != nil {
		return nil, fmt.Errorf("failed parsing DLQ URL %q: %w", dlqURL, err)
	}
	right = queueCell{Link: &link{URL: "/queue/" + dlqName, Text: dlqName}}

	a, err := redriver.ARNFromURL(dlqURL)
	if err != nil {
		return nil, fmt.Errorf("failed converting URL %q to ARN: %w", dlqURL, err)
	}
	dlqARN := a.String()
	var bound, unbound, elsewhere []link
	for _, sourceARN := range DLQrp.SourceQueueARNs {
		sa, err := arn.Parse(sourceARN)
		if err != nil {
			return nil, fmt.Errorf("failed parsing %q as an ARN: %w", sourceARN, err)
		}
		sourceURL, err := redriver.URLFromARN(sa)
		if err != nil {
			return nil, fmt.Errorf("failed converting ARN %q for URL: %w", sa.String(), err)
		}
		sourceName := sa.Resource
		sourceQRP, ok := sources[sourceURL]
		link := link{
			URL:  "/queue/" + sourceName,
			Text: sourceName,
		}
		if ok {
			switch sourceQRP.DeadLetterTargetARN {
			case "":
				unbound = append(unbound, link)
			case dlqARN:
				bound = append(bound, link)
			default:
				elsewhere = append(elsewhere, link)
			}
		} else {
			unbound = append(unbound, link)
		}
	}
	left.Links = [][]link{bound, elsewhere, unbound}

	return &QueueRow{left, right}, nil
}

func prepareBindings(rows []QueueRow, DLQs, sources map[string]redriver.QueueRedrivePolicies) ([]QueueRow, error) {
	for dlqURL, DLQrp := range DLQs {
		row, err := prepareBinding(dlqURL, DLQrp, sources)
		if err != nil || row == nil {
			return nil, fmt.Errorf("failed binding DLQ %s: %w", dlqURL, err)
		}
		rows = append(rows, *row)
	}
	return rows, nil
}
