package main

import (
	"context"
	"log"
	"math/big"
	"sync"
	"time"

	"git.ali33.ru/fcg-xvii/curve/v2"
	"git.ali33.ru/fcg-xvii/curve/v2/dhellman"
	"git.ali33.ru/fcg-xvii/curve/v2/elgamal"
	"git.ali33.ru/fcg-xvii/curve/v2/tools"
	"git.ali33.ru/fcg-xvii/go-tools/json"
)

func EventSingle(Type, method string, contextID int) *Event {
	res := &Event{
		ContextID: contextID,
		Type:      Type,
		Value:     json.Map{},
	}
	if len(method) > 0 {
		res.Value["method"] = method
	}
	return res
}

type Event struct {
	ContextID int
	Type      string
	IsError   bool
	Value     json.Map
}

func (s *Event) Map() json.Map {
	return json.Map{
		"context_id": s.ContextID,
		"type":       "event",
		"name":       s.Type,
		"is_error":   s.IsError,
		"data":       s.Value,
	}

}

func (s *Event) MarshalJSON() ([]byte, error) {
	return s.Map().JSON(), nil
}

func (s *Event) String() string {
	return s.Map().JSONPrettyString()
}

func MethodExec(p1, p2 curve.KeyPair, ch chan<- *Event, ctx context.Context, message, method string, contextID int) {
	// генерация ключей
	t := time.Now()
	encoded, _ := p1.MessageEncode([]byte(message), p2.KeyPublic())
	select {
	case <-ctx.Done():
		return
	default:
		ch <- &Event{
			ContextID: contextID,
			Type:      "Сообщение зашифровано пользователем 1",
			Value: json.Map{
				"method":  method,
				"text":    message,
				"encoded": encoded,
				"time":    int64(time.Since(t) / time.Millisecond),
			},
		}
	}
	t = time.Now()
	decoded, _ := p2.MessageDecode(encoded, p1.KeyPublic())
	select {
	case <-ctx.Done():
		return
	default:
		ch <- &Event{
			ContextID: contextID,
			Type:      "Сообщение расшифровано пользователем 2",
			Value: json.Map{
				"method":  method,
				"text":    message,
				"decoded": string(decoded),
				"time":    int64(time.Since(t) / time.Millisecond),
			},
		}
	}
	// Атака посредника.
	ch <- EventSingle("Атака посредника. Запущен подбор ключа", method, contextID)
	t = time.Now()
	if aPair, err := p2.KeyPublic().Attack(ctx); err != nil {
		ch <- &Event{
			ContextID: contextID,
			Type:      "Атака посредника",
			IsError:   true,
			Value: json.Map{
				"method": method,
				"text":   err.Error(),
			},
		}
	} else {
		decoded, _ = aPair.MessageDecode(encoded, p1.KeyPublic())
		ch <- &Event{
			ContextID: contextID,
			Type:      "Атака посредника. Ключ найден",
			Value: json.Map{
				"method":  method,
				"pair":    p2,
				"cracked": aPair,
				"time":    int64(time.Since(t) / time.Millisecond),
				"source":  message,
				"decoded": string(decoded),
			},
		}
	}
}

func MethodCurve(randNum *big.Int, message string, ctx context.Context, contextID int) <-chan *Event {
	ch := make(chan *Event)
	go func() {
		defer close(ch)
		// Поиск параметров кривой
		a, b, p := tools.CurveRandomParams(randNum)
		select {
		case <-ctx.Done():
			return
		default:
			ch <- &Event{
				ContextID: contextID,
				Type:      "Параметры кривой определены",
				Value: json.Map{
					"a": a,
					"b": b,
					"p": p,
				},
			}
		}
		ch <- EventSingle("Инициализация кривой", "", contextID)
		t := time.Now()
		c, err := curve.NewCurve(a, b, p, ctx)
		if err != nil {
			ch <- &Event{
				ContextID: contextID,
				Type:      "Инициализация кривой",
				IsError:   true,
				Value: json.Map{
					"text": err.Error(),
				},
			}
			return
		}
		ch <- &Event{
			ContextID: contextID,
			Type:      "Кривая инициализирована",
			Value: json.Map{
				"g":    c.G(),
				"time": int64(time.Since(t) / time.Millisecond),
			},
		}
		var wg sync.WaitGroup
		wg.Add(2)
		// метод Диффи-Хеллмана
		go func() {
			defer wg.Done()
			// Генерация ключей
			ch <- EventSingle("Генерация пар ключей", "Диффи-Хеллман", contextID)
			t := time.Now()
			p1, _ := dhellman.CurveKeyPair(c)
			p2, _ := dhellman.CurveKeyPair(c)
			select {
			case <-ctx.Done():
				return
			default:
				ch <- &Event{
					ContextID: contextID,
					Type:      "Пары ключей сгенерированы",
					Value: json.Map{
						"method": "Диффи-Хеллман",
						"u1":     p1,
						"u2":     p2,
						"time":   int64(time.Since(t) / time.Millisecond),
					},
				}
			}
			MethodExec(p1, p2, ch, ctx, message, "Диффи-Хеллман", contextID)
		}()
		// метод Эль-Гамаля
		go func() {
			defer wg.Done()
			// Генерация таблицы
			ch <- EventSingle("Построение таблицы (символ) -> (точка на кривой) для шифрования/расшифровки сообщений", "Эль-Гамаль", contextID)
			t := time.Now()
			cc, err := elgamal.NewCurve(c, ctx)
			if err != nil {
				ch <- &Event{
					ContextID: contextID,
					Type:      "Построение таблицы (символ) -> (точка на кривой) для шифрования/расшифровки сообщений",
					IsError:   true,
					Value: json.Map{
						"method": "Эль-Гамаль",
						"text":   err.Error(),
					},
				}
				return
			}
			ch <- &Event{
				ContextID: contextID,
				Type:      "Тиблица символов построена",
				Value: json.Map{
					"method": "Эль-Гамаль",
					"time":   int64(time.Since(t) / time.Millisecond),
					"curve":  cc.Map(),
				},
			}
			ch <- EventSingle("Генерация пар ключей", "Эль-Гамаль", contextID)
			t = time.Now()
			p1, _ := elgamal.CurveKeyPair(cc, ctx)
			p2, _ := elgamal.CurveKeyPair(cc, ctx)
			select {
			case <-ctx.Done():
				return
			default:
				ch <- &Event{
					ContextID: contextID,
					Type:      "Пары ключей сгенерированы",
					Value: json.Map{
						"method": "Эль-Гамаль",
						"u1":     p1,
						"u2":     p2,
						"time":   int64(time.Since(t) / time.Millisecond),
					},
				}
			}
			MethodExec(p1, p2, ch, ctx, message, "Эль-Гамаль", contextID)
		}()
		wg.Wait()
		ch <- EventSingle("Все работы завершены.", "", contextID)
		log.Println("FINISH")
		contextStore.Delete(contextID)
	}()
	return ch
}
