package auth

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/prism/proxy/internal/store"
)

const (
	defaultMaxAttempts = 5
	defaultLockMinutes = 15
)

type LoginConfig struct {
	MaxAttempts  int
	Window       time.Duration
	LockDuration time.Duration
}

type LoginResult struct {
	Allowed    bool
	Locked     bool
	Remaining  int
	RetryAfter time.Duration
}

type LoginLimiter struct {
	store *store.Store
}

func NewLoginLimiter(st *store.Store) *LoginLimiter {
	return &LoginLimiter{store: st}
}

func ParseLoginConfig(settings map[string]string) LoginConfig {
	maxAttempts := parsePositiveInt(settings["login_max_attempts"], defaultMaxAttempts)
	lockMin := parsePositiveInt(settings["login_lock_minutes"], defaultLockMinutes)
	lock := time.Duration(lockMin) * time.Minute
	return LoginConfig{
		MaxAttempts:  maxAttempts,
		Window:       lock,
		LockDuration: lock,
	}
}

func (l *LoginLimiter) Check(ctx context.Context, ip string, cfg LoginConfig) (LoginResult, error) {
	attempt, err := l.store.GetLoginAttempt(ctx, ip)
	if err != nil {
		return LoginResult{}, err
	}
	if attempt == nil {
		return LoginResult{Allowed: true, Remaining: cfg.MaxAttempts}, nil
	}
	now := time.Now()
	if attempt.LockedUntil != nil && now.Before(*attempt.LockedUntil) {
		return LoginResult{
			Allowed:    false,
			Locked:     true,
			RetryAfter: attempt.LockedUntil.Sub(now),
		}, nil
	}
	remaining := cfg.MaxAttempts - attempt.FailCount
	if remaining < 0 {
		remaining = 0
	}
	if attempt.FailCount > 0 && now.Sub(attempt.WindowStart) > cfg.Window {
		remaining = cfg.MaxAttempts
	}
	return LoginResult{Allowed: true, Remaining: remaining}, nil
}

func (l *LoginLimiter) RecordFailure(ctx context.Context, ip string, cfg LoginConfig) (LoginResult, error) {
	now := time.Now()
	attempt, err := l.store.GetLoginAttempt(ctx, ip)
	if err != nil {
		return LoginResult{}, err
	}
	if attempt != nil && attempt.LockedUntil != nil && now.Before(*attempt.LockedUntil) {
		return LoginResult{
			Allowed:    false,
			Locked:     true,
			RetryAfter: attempt.LockedUntil.Sub(now),
		}, nil
	}

	failCount := 1
	windowStart := now
	if attempt != nil {
		if attempt.LockedUntil != nil || now.Sub(attempt.WindowStart) <= cfg.Window {
			if now.Sub(attempt.WindowStart) <= cfg.Window {
				failCount = attempt.FailCount + 1
				windowStart = attempt.WindowStart
			}
		}
	}

	var lockedUntil *time.Time
	locked := false
	if failCount >= cfg.MaxAttempts {
		t := now.Add(cfg.LockDuration)
		lockedUntil = &t
		locked = true
		_ = l.store.AddAuditLog(ctx, "login_lock", "auth", fmt.Sprintf("ip=%s locked %v", ip, cfg.LockDuration))
	}
	if err := l.store.SetLoginAttempt(ctx, ip, store.LoginAttempt{
		FailCount:   failCount,
		WindowStart: windowStart,
		LockedUntil: lockedUntil,
	}); err != nil {
		return LoginResult{}, err
	}

	remaining := cfg.MaxAttempts - failCount
	if remaining < 0 {
		remaining = 0
	}
	res := LoginResult{Allowed: !locked, Remaining: remaining, Locked: locked}
	if locked && lockedUntil != nil {
		res.RetryAfter = lockedUntil.Sub(now)
	}
	return res, nil
}

func (l *LoginLimiter) RecordSuccess(ctx context.Context, ip string) error {
	return l.store.ClearLoginAttempt(ctx, ip)
}

func parsePositiveInt(raw string, fallback int) int {
	n, err := strconv.Atoi(raw)
	if err != nil || n <= 0 {
		return fallback
	}
	return n
}
