package security

import (
	"net/http"
	"strings"

	"github.com/labstack/echo/v4"
)

// CSPConfig holds CSP configuration
type CSPConfig struct {
	DefaultSrc []string
	ScriptSrc  []string
	StyleSrc   []string
	ImgSrc     []string
	ConnectSrc []string
	FontSrc    []string
	ObjectSrc  []string
	MediaSrc   []string
	FrameSrc   []string
	ReportURI  string
	ReportOnly bool
}

// DefaultCSPConfig returns a secure default CSP configuration
func DefaultCSPConfig() *CSPConfig {
	return &CSPConfig{
		DefaultSrc: []string{"'self'"},
		ScriptSrc:  []string{"'self'"},
		StyleSrc:   []string{"'self'"},
		ImgSrc:     []string{"'self'", "data:", "https:", "http:"},
		ConnectSrc: []string{"'self'", "https://api.opplens.com"},
		FontSrc:    []string{"'self'", "https://fonts.gstatic.com"},
		ObjectSrc:  []string{"'none'"},
		MediaSrc:   []string{"'self'"},
		FrameSrc:   []string{"'none'"},
		ReportURI:  "/api/v1/csp-report",
		ReportOnly: false,
	}
}

// CSPSource represents a CSP directive source
type CSPSource string

// CSPDirective represents a CSP directive
type CSPDirective struct {
	Name    string
	Sources []CSPSource
}

// CSPMiddleware creates Echo middleware for Content Security Policy
func CSPMiddleware(config *CSPConfig) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Build CSP header
			directives := []string{}

			if len(config.DefaultSrc) > 0 {
				directives = append(directives, "default-src "+strings.Join(toStringSlice(config.DefaultSrc), " "))
			}

			if len(config.ScriptSrc) > 0 {
				directives = append(directives, "script-src "+strings.Join(toStringSlice(config.ScriptSrc), " "))
			}

			if len(config.StyleSrc) > 0 {
				directives = append(directives, "style-src "+strings.Join(toStringSlice(config.StyleSrc), " "))
			}

			if len(config.ImgSrc) > 0 {
				directives = append(directives, "img-src "+strings.Join(toStringSlice(config.ImgSrc), " "))
			}

			if len(config.ConnectSrc) > 0 {
				directives = append(directives, "connect-src "+strings.Join(toStringSlice(config.ConnectSrc), " "))
			}

			if len(config.FontSrc) > 0 {
				directives = append(directives, "font-src "+strings.Join(toStringSlice(config.FontSrc), " "))
			}

			if len(config.ObjectSrc) > 0 {
				directives = append(directives, "object-src "+strings.Join(toStringSlice(config.ObjectSrc), " "))
			}

			if len(config.MediaSrc) > 0 {
				directives = append(directives, "media-src "+strings.Join(toStringSlice(config.MediaSrc), " "))
			}

			if len(config.FrameSrc) > 0 {
				directives = append(directives, "frame-src "+strings.Join(toStringSlice(config.FrameSrc), " "))
			}

			if config.ReportURI != "" {
				directives = append(directives, "report-uri "+config.ReportURI)
			}

			cspHeader := strings.Join(directives, "; ")

			if config.ReportOnly {
				c.Response().Header().Set("Content-Security-Policy-Report-Only", cspHeader)
			} else {
				c.Response().Header().Set("Content-Security-Policy", cspHeader)
			}

			return next(c)
		}
	}
}

// CSPMiddlewareForPaths applies CSP middleware based on request path prefix.
// Requests matching no prefix proceed without a CSP header.
func CSPMiddlewareForPaths(adminPrefix string, adminConfig *CSPConfig, publicPrefix string, publicConfig *CSPConfig) echo.MiddlewareFunc {
	adminMW := CSPMiddleware(adminConfig)
	publicMW := CSPMiddleware(publicConfig)

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			path := c.Request().URL.Path
			switch {
			case strings.HasPrefix(path, adminPrefix):
				return adminMW(next)(c)
			case strings.HasPrefix(path, publicPrefix):
				return publicMW(next)(c)
			default:
				return next(c)
			}
		}
	}
}

// CSPReportHandler handles CSP violation reports
func CSPReportHandler(c echo.Context) error {
	var report struct {
		CSPReport struct {
			DocumentURI        string `json:"document-uri"`
			ViolatedDirective  string `json:"violated-directive"`
			EffectiveDirective string `json:"effective-directive"`
			OriginalPolicy     string `json:"original-policy"`
			BlockedURI         string `json:"blocked-uri"`
			StatusCode         int    `json:"status-code"`
		} `json:"csp-report"`
	}

	if err := c.Bind(&report); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid CSP report"})
	}

	// Log CSP violation (implement proper logging here)
	// logger.Warn("CSP Violation", map[string]interface{}{
	//     "document_uri":        report.CSPReport.DocumentURI,
	//     "violated_directive":  report.CSPReport.ViolatedDirective,
	//     "blocked_uri":         report.CSPReport.BlockedURI,
	// })

	return c.JSON(http.StatusOK, map[string]string{"status": "reported"})
}

func toStringSlice(sources []string) []string {
	result := make([]string, len(sources))
	for i, src := range sources {
		result[i] = string(src)
	}
	return result
}
