package logging

import (
	"bufio"
	"errors"
	"io"
	"sync"
	"time"
)

type writeCloser interface {
	io.Writer
	io.Closer
}

type fileSink struct {
	mu        sync.Mutex
	writer    writeCloser
	buffer    *bufio.Writer
	stop      chan struct{}
	done      chan struct{}
	close     sync.Once
	closeErr  error
	stickyErr error
	closed    bool
}

func newFileSink(writer writeCloser, bufferSize int, flushInterval time.Duration) *fileSink {
	sink := &fileSink{writer: writer}
	if bufferSize <= 0 {
		return sink
	}
	sink.buffer = bufio.NewWriterSize(writer, bufferSize)
	sink.stop = make(chan struct{})
	sink.done = make(chan struct{})
	go sink.flushLoop(flushInterval)
	return sink
}

func (sink *fileSink) Write(data []byte) (int, error) {
	sink.mu.Lock()
	defer sink.mu.Unlock()
	if sink.closed {
		return 0, io.ErrClosedPipe
	}
	if sink.buffer != nil {
		written, err := sink.buffer.Write(data)
		sink.recordError(err)
		return written, err
	}
	written, err := sink.writer.Write(data)
	sink.recordError(err)
	return written, err
}

func (sink *fileSink) Flush() error {
	sink.mu.Lock()
	defer sink.mu.Unlock()
	if sink.closed {
		return io.ErrClosedPipe
	}
	if sink.buffer == nil {
		return sink.stickyErr
	}
	err := sink.buffer.Flush()
	sink.recordError(err)
	return sink.stickyErr
}

func (sink *fileSink) Close() error {
	sink.close.Do(func() {
		if sink.stop != nil {
			close(sink.stop)
		}
		sink.mu.Lock()
		sink.closed = true
		if sink.buffer != nil {
			sink.recordError(sink.buffer.Flush())
		}
		sink.closeErr = errors.Join(sink.stickyErr, sink.writer.Close())
		sink.mu.Unlock()
		if sink.done != nil {
			<-sink.done
		}
	})
	return sink.closeErr
}

func (sink *fileSink) recordError(err error) {
	if err != nil && sink.stickyErr == nil {
		sink.stickyErr = err
	}
}

func (sink *fileSink) flushLoop(interval time.Duration) {
	defer close(sink.done)
	ticker := time.NewTicker(interval)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			_ = sink.Flush()
		case <-sink.stop:
			return
		}
	}
}
