package logging

import (
	"fmt"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"sync"

	"github.com/tidbcloud/AgentTeam/internal/config"
)

// Pipeline owns the process-wide handler and any file writer lifecycle.
type Pipeline struct {
	logger   *slog.Logger
	fileSink *fileSink
	close    sync.Once
	closeErr error
}

// Open builds one operational logging pipeline. The returned logger defaults
// records without an explicit domain to system.
func Open(cfg config.LogConfig, stdout io.Writer) (*Pipeline, error) {
	cfg = cfg.Normalized()
	if err := cfg.Validate(); err != nil {
		return nil, err
	}

	var sink *fileSink
	var writer io.Writer
	switch cfg.Output {
	case config.LogOutputStdout:
		if stdout == nil {
			return nil, fmt.Errorf("initialize logging: stdout writer is required")
		}
		writer = stdout
	case config.LogOutputFile, config.LogOutputBoth:
		rotated, err := openRotatedFile(cfg)
		if err != nil {
			return nil, err
		}
		sink = rotated
		writer = sink
		if cfg.Output == config.LogOutputBoth && !writerTargetsPath(stdout, cfg.FilePath) {
			if stdout == nil {
				_ = sink.Close()
				return nil, fmt.Errorf("initialize logging: stdout writer is required for AGENTTEAM_LOG_OUTPUT=both")
			}
			writer = io.MultiWriter(stdout, sink)
		}
	}

	options := &slog.HandlerOptions{Level: cfg.Level}
	var handler slog.Handler
	if cfg.Format == config.LogFormatText {
		handler = slog.NewTextHandler(writer, options)
	} else {
		handler = slog.NewJSONHandler(writer, options)
	}
	var flush func() error
	if sink != nil {
		flush = sink.Flush
	}
	handler = WrapHandler(handler, flush)
	return &Pipeline{logger: slog.New(handler), fileSink: sink}, nil
}

func (pipeline *Pipeline) Logger() *slog.Logger {
	if pipeline == nil {
		return nil
	}
	return pipeline.logger
}

func (pipeline *Pipeline) Flush() error {
	if pipeline == nil || pipeline.fileSink == nil {
		return nil
	}
	return pipeline.fileSink.Flush()
}

func (pipeline *Pipeline) Close() error {
	if pipeline == nil {
		return nil
	}
	pipeline.close.Do(func() {
		if pipeline.fileSink != nil {
			pipeline.closeErr = pipeline.fileSink.Close()
		}
	})
	return pipeline.closeErr
}

func openRotatedFile(cfg config.LogConfig) (*fileSink, error) {
	parent := filepath.Dir(cfg.FilePath)
	if err := os.MkdirAll(parent, 0o700); err != nil {
		return nil, fmt.Errorf("create log directory: %w", err)
	}
	if err := preflightLogPath(cfg.FilePath); err != nil {
		return nil, err
	}
	rotated, err := newRotatingFile(rotatingFileConfig{
		path:        cfg.FilePath,
		maxBytes:    int64(cfg.MaxSizeMB) * 1024 * 1024,
		maxDays:     cfg.MaxDays,
		maxBackups:  cfg.MaxBackups,
		compression: cfg.Compression,
	})
	if err != nil {
		return nil, fmt.Errorf("open rotating log file: %w", err)
	}
	return newFileSink(rotated, cfg.BufferSizeKB*1024, cfg.FlushInterval), nil
}

func preflightLogPath(path string) error {
	file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
	if err != nil {
		return fmt.Errorf("open log file: %w", err)
	}
	if err := file.Chmod(0o600); err != nil {
		_ = file.Close()
		return fmt.Errorf("restrict log file permissions: %w", err)
	}
	if err := file.Close(); err != nil {
		return fmt.Errorf("close log file preflight: %w", err)
	}

	probe, err := os.CreateTemp(filepath.Dir(path), ".agentteam-log-preflight-*")
	if err != nil {
		return fmt.Errorf("create log rotation probe: %w", err)
	}
	probePath := probe.Name()
	renamedPath := probePath + ".renamed"
	defer os.Remove(probePath)
	defer os.Remove(renamedPath)
	if err := probe.Chmod(0o600); err != nil {
		_ = probe.Close()
		return fmt.Errorf("restrict log rotation probe permissions: %w", err)
	}
	if _, err := probe.Write([]byte("preflight")); err != nil {
		_ = probe.Close()
		return fmt.Errorf("write log rotation probe: %w", err)
	}
	if err := probe.Close(); err != nil {
		return fmt.Errorf("close log rotation probe: %w", err)
	}
	if err := os.Rename(probePath, renamedPath); err != nil {
		return fmt.Errorf("rename log rotation probe: %w", err)
	}
	if err := os.Remove(renamedPath); err != nil {
		return fmt.Errorf("remove log rotation probe: %w", err)
	}
	return nil
}

func writerTargetsPath(writer io.Writer, path string) bool {
	file, ok := writer.(*os.File)
	if !ok || file == nil {
		return false
	}
	writerInfo, err := file.Stat()
	if err != nil {
		return false
	}
	pathInfo, err := os.Stat(path)
	if err != nil {
		return false
	}
	return os.SameFile(writerInfo, pathInfo)
}
