package pipe

import (
	"context"
	"fmt"
	"os"
	"time"

	"gpio-monitor/internal/adapter"
	"gpio-monitor/internal/logger"
)

type PipeReader struct {
	buf         *adapter.RingBuffer
	dataLogger  *logger.RotatingLogger
	humanLogger *logger.HumanLogger
	monitor     *logger.Monitor
	eventLog    *logger.EventLogger

	// Для дедупликации логов
	lastData      logger.GPIOData
	lastLogTime   time.Time
	lastAlertTime map[byte]time.Time

	// Настройки
	alertCooldownSec    time.Duration
	humanLogIntervalSec time.Duration

	// Флаги состояния
	pipeState          string // "unknown", "found", "not_found"
	lastLoggedState    string // для отслеживания изменений
}

func NewPipeReader(
	buf *adapter.RingBuffer,
	dataLogger *logger.RotatingLogger,
	humanLogger *logger.HumanLogger,
	monitor *logger.Monitor,
	eventLog *logger.EventLogger,
	config *logger.Config,
) *PipeReader {
	return &PipeReader{
		buf:                 buf,
		dataLogger:          dataLogger,
		humanLogger:         humanLogger,
		monitor:             monitor,
		eventLog:            eventLog,
		lastAlertTime:       make(map[byte]time.Time),
		alertCooldownSec:    config.AlertCooldownSec,
		humanLogIntervalSec: config.HumanLogIntervalSec,
		pipeState:           "unknown",
		lastLoggedState:     "unknown",
	}
}


func (pr *PipeReader) logStateChange(newState string, eventName string) {
	if newState != pr.lastLoggedState {
		if pr.eventLog != nil {
			pr.eventLog.Event(eventName)
		}
		pr.lastLoggedState = newState
	}
}

func (pr *PipeReader) Start(ctx context.Context, pipePath string) {
	go func() {
		buffer := make([]byte, 4096)

		for {
			select {
			case <-ctx.Done():
				return
			default:
			}

			if _, err := os.Stat(pipePath); os.IsNotExist(err) {
				// Логируем ТОЛЬКО при смене состояния
				if pr.pipeState != "not_found" {
					pr.pipeState = "not_found"
					pr.logStateChange("not_found", "PIPE_НЕ_НАЙДЕН")
				}
				time.Sleep(2 * time.Second)
				continue
			}

			f, err := os.OpenFile(pipePath, os.O_RDONLY, 0)
			if err != nil {
				if pr.pipeState != "error" {
					pr.pipeState = "error"
					pr.logStateChange("error", "ОШИБКА_ОТКРЫТИЯ_PIPE")
				}
				time.Sleep(time.Second)
				continue
			}

			// Pipe успешно открыт
			if pr.pipeState != "found" {
				pr.pipeState = "found"
				pr.logStateChange("found", "PIPE_ПОДКЛЮЧЕН")
			}

			for {
				n, err := f.Read(buffer)
				if err != nil {
					f.Close()
					// Отключаемся только если были подключены
					if pr.pipeState == "found" {
						pr.pipeState = "disconnected"
						pr.logStateChange("disconnected", "PIPE_ОТКЛЮЧЕН")
					}
					break
				}

				// Если были в состоянии ошибки/отключения, восстанавливаемся
				if pr.pipeState != "found" {
					pr.pipeState = "found"
					pr.logStateChange("found", "PIPE_ПОДКЛЮЧЕН")
				}

				for i := 0; i < n; i++ {
					rawByte := buffer[i]
					now := time.Now()

					// Парсим данные
					data := logger.ParseGPIO(rawByte)

					// 1. В RAM для UI (сырое значение)
					pr.buf.Write(rawByte)

					// 2. На диск для архива (бинарный)
					if pr.dataLogger != nil {
						pr.dataLogger.Write(logger.Sample{
							Timestamp: now.UnixMicro(),
							Value:     rawByte,
						})
					}

					// 3. Human-readable лог (при изменении состояния или раз в N секунд)
					if pr.humanLogger != nil {
						shouldLog := false

						// Логируем если изменилось количество или сила
						if data.Count != pr.lastData.Count || data.Strength != pr.lastData.Strength {
							shouldLog = true
						}

						// Или если прошло больше N секунд с последнего лога
						if now.Sub(pr.lastLogTime) >= pr.humanLogIntervalSec {
							shouldLog = true
						}

						if shouldLog {
							strengthName := logger.GetStrengthName(data.Strength)
							humanLine := fmt.Sprintf(
								"[%s] Обнаружение: %d объектов | Сила: %s (уровень %d) | Сырое: 0x%02X (%d)\n",
								now.Format("2006-01-02 15:04:05.000"),
								data.Count,
								strengthName,
								data.Strength,
								rawByte,
								rawByte,
							)
							pr.humanLogger.Write(humanLine)
							pr.lastData = data
							pr.lastLogTime = now
						}
					}

					// 4. Алерт если количество обнаружений > 10
					if data.IsHighCount {
						pr.handleAlert(data, now)
					}

					// 5. Обновляем watchdog
					if pr.monitor != nil {
						pr.monitor.RecordWrite()
					}
				}
			}

			time.Sleep(1 * time.Second)
		}
	}()
}

func (pr *PipeReader) handleAlert(data logger.GPIOData, timestamp time.Time) {
	// Anti-spam: не чаще 1 алерта в N секунд для одинакового количества
	key := data.Count
	if last, exists := pr.lastAlertTime[key]; exists {
		if timestamp.Sub(last) < pr.alertCooldownSec {
			return
		}
	}
	pr.lastAlertTime[key] = timestamp

	strengthName := logger.GetStrengthName(data.Strength)

	// 1. В event log (JSON)
	if pr.eventLog != nil {
		pr.eventLog.Event(fmt.Sprintf("ОБНАРУЖЕНО_%d_ОБЪЕКТОВ_СИЛА_%d", data.Count, data.Strength))
	}

	// 2. В human-readable лог с предупреждением
	if pr.humanLogger != nil {
		alertLine := fmt.Sprintf(
			"[%s] ВНИМАНИЕ: Обнаружено превышение! %d объектов | Сила: %s (уровень %d)\n",
			timestamp.Format("2006-01-02 15:04:05.000"),
			data.Count,
			strengthName,
			data.Strength,
		)
		pr.humanLogger.Write(alertLine)
	}
}