package processhttp

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type ServerPolicy struct {
	ReadHeaderTimeout time.Duration
	ReadTimeout       time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration
}

type ProcessConfig struct {
	Role         string
	Version      string
	Address      string
	Capabilities []string
	Handler      http.Handler
	Ready        func() bool
	BeginDrain   func()
	Policy       ServerPolicy
}

// Process owns the app-blind HTTP listener, operational probes, readiness,
// drain ordering, and transport shutdown used by independently runnable L1
// services. Product code owns only its handler and semantic readiness input.
type Process struct {
	config   ProcessConfig
	server   *http.Server
	listener net.Listener
	done     chan struct{}
	serveErr error
	stopErr  error
	started  bool
	live     atomic.Bool
	draining atomic.Bool
	mu       sync.Mutex
	stopOnce sync.Once
}

func NewProcess(config ProcessConfig) (*Process, error) {
	if strings.TrimSpace(config.Address) == "" || config.Address != strings.TrimSpace(config.Address) || config.Handler == nil {
		return nil, errors.New("processhttp: address and product handler are required")
	}
	if config.Policy.ReadHeaderTimeout <= 0 || config.Policy.ReadTimeout <= 0 || config.Policy.WriteTimeout <= 0 || config.Policy.IdleTimeout <= 0 {
		return nil, errors.New("processhttp: every HTTP server timeout must be explicit and positive")
	}
	process := &Process{config: config, done: make(chan struct{})}
	operations, err := NewHandler(Config{
		Role: config.Role, Version: config.Version, Capabilities: config.Capabilities,
		Live: process.live.Load, Ready: process.Ready,
	})
	if err != nil {
		return nil, err
	}
	mux := http.NewServeMux()
	for _, path := range []string{LivePath, ReadyPath, VersionPath, CapabilitiesPath} {
		mux.Handle(path, operations)
	}
	mux.Handle("/", RequestResponseOnly(config.Handler))
	process.server = &http.Server{
		Handler: mux, ReadHeaderTimeout: config.Policy.ReadHeaderTimeout,
		ReadTimeout: config.Policy.ReadTimeout, WriteTimeout: config.Policy.WriteTimeout,
		IdleTimeout: config.Policy.IdleTimeout,
	}
	return process, nil
}

func (p *Process) Start() error {
	if p == nil || p.server == nil {
		return errors.New("processhttp: process is unavailable")
	}
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.started {
		return errors.New("processhttp: process has already started")
	}
	listener, err := net.Listen("tcp", p.config.Address)
	if err != nil {
		return fmt.Errorf("processhttp: listen: %w", err)
	}
	p.listener = listener
	p.started = true
	p.live.Store(true)
	go func() {
		err := p.server.Serve(listener)
		if errors.Is(err, http.ErrServerClosed) {
			err = nil
		}
		p.live.Store(false)
		p.mu.Lock()
		p.serveErr = err
		p.mu.Unlock()
		close(p.done)
	}()
	return nil
}

func (p *Process) Ready() bool {
	return p != nil && p.live.Load() && !p.draining.Load() && (p.config.Ready == nil || p.config.Ready())
}

func (p *Process) BeginDrain() {
	if p == nil || !p.draining.CompareAndSwap(false, true) {
		return
	}
	if p.config.BeginDrain != nil {
		p.config.BeginDrain()
	}
}

func (p *Process) Address() string {
	if p == nil {
		return ""
	}
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.listener == nil {
		return ""
	}
	return p.listener.Addr().String()
}

func (p *Process) Wait() error {
	if p == nil {
		return errors.New("processhttp: process is unavailable")
	}
	p.mu.Lock()
	started := p.started
	done := p.done
	p.mu.Unlock()
	if !started {
		return errors.New("processhttp: process has not started")
	}
	<-done
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.serveErr
}

func (p *Process) Shutdown(ctx context.Context) error {
	if p == nil || p.server == nil {
		return nil
	}
	if ctx == nil {
		return errors.New("processhttp: shutdown context is required")
	}
	p.mu.Lock()
	started := p.started
	p.mu.Unlock()
	if !started {
		return errors.New("processhttp: process has not started")
	}
	p.stopOnce.Do(func() {
		p.BeginDrain()
		if err := p.server.Shutdown(ctx); err != nil {
			_ = p.server.Close()
			p.mu.Lock()
			p.stopErr = err
			p.mu.Unlock()
		}
	})
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.stopErr
}
