package processors

import (
	"fmt"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/sirupsen/logrus"
	"github.com/titansys/appy-builder/internal/models"
	templateModels "github.com/titansys/appy-builder/internal/platform/shared/models"
	"github.com/titansys/appy-builder/internal/platform/shared/validation"
)

// DataBuilder builds template data from build configuration
type DataBuilder struct {
	logger      *logrus.Logger
	themeProc   *ThemeProcessor
	navProc     *NavigationProcessor
	assetProc   *AssetDownloader
	appyBaseURL string
}

// NewDataBuilder creates a new data builder
func NewDataBuilder(logger *logrus.Logger, cacheDir string, appyBaseURL string) *DataBuilder {
	return &DataBuilder{
		logger:      logger,
		themeProc:   NewThemeProcessor(),
		navProc:     NewNavigationProcessor(),
		assetProc:   NewAssetDownloader(logger, cacheDir),
		appyBaseURL: appyBaseURL,
	}
}

// normalizeAssetURL converts relative storage paths to absolute URLs
// Handles: relative paths, absolute URLs, hex colors (defensive)
func (d *DataBuilder) normalizeAssetURL(path string) string {
	if path == "" {
		return ""
	}

	// Already absolute URL - return as-is
	if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
		return path
	}

	// Hex color code - return as-is (defensive check to prevent download attempts)
	if strings.HasPrefix(path, "#") {
		return path
	}

	// Relative path - prepend Appy base URL
	cleanPath := strings.TrimPrefix(path, "/")
	return fmt.Sprintf("%s/storage/%s", d.appyBaseURL, cleanPath)
}

// BuildTemplateData converts BuildConfig to TemplateData
func (d *DataBuilder) BuildTemplateData(req models.BuildRequest, projectDir string) (*templateModels.TemplateData, error) {
	config := req.Config

	data := &templateModels.TemplateData{
		// Basic Info
		AppName:    config.AppName,
		AppID:      config.AppID,
		WebsiteURL: config.WebsiteURL,

		// Template Type
		LayoutTemplate: config.LayoutTemplate,

		// Configuration
		Orientation:    d.mapOrientation(config.Orientation),
		UserAgent:      config.UserAgent,
		ShowPageTitle:  config.ShowPageTitle,
		TextColorLight: d.stripHashPrefix(config.TextColorLight),
		TextColorDark:  d.stripHashPrefix(config.TextColorDark),
		ProgressType:   d.mapProgressIndicator(config.ProgressIndicator),
		ProgressColor:  config.ProgressColor,
		HomeURL:        config.WebsiteURL,

		// Status Bar - light icons when text color is light (for dark backgrounds)
		LightStatusBarLight: d.isLightColor(config.TextColorLight),
		LightStatusBarDark:  d.isLightColor(config.TextColorDark),

		// WebView Settings
		EnableJavaScript: config.EnableJavaScript,
		EnableDOMStorage: config.EnableDOMStorage,
		EnableZoom:       config.EnableZoom,
		EnableCache:      config.EnableCache,

		// Theme Configuration
		EnableDynamicColors: config.EnableDynamicColors,

		// Custom Code
		CustomCSS: config.CustomCSS,
		CustomJS:  config.CustomJavaScript,

		// Navigation Config
		PullToRefreshEnabled:   d.getPullToRefreshEnabled(config),
		SwipeNavigationEnabled: config.SwipeNavigation,
		PreserveTabState:       config.PreserveTabState,

		// Permissions
		Permissions: templateModels.PermissionConfig{
			Location:     config.PermissionLocation,
			Camera:       config.PermissionCamera,
			Storage:      config.PermissionStorage,
			RecordAudio:  config.PermissionRecordAudio,
			ReadContacts: config.PermissionReadContacts,
			Vibrate:      config.PermissionVibrate,
		},

		// Firebase/FCM
		FirebaseEnabled:    config.FirebaseEnabled,
		GoogleServicesJSON: config.GoogleServicesJSON,

		// Versioning
		VersionName:        "1.0.0",
		VersionCode:        d.getVersionCode(config),
		AndroidVersionName: d.getAndroidVersionName(config),

		// Build Config
		BuildConfigJSON:      config.BuildConfigJSON,
		EnvironmentVariables: config.EnvironmentVariables,
		BuildFormat:          d.getBuildFormat(config),

		// Original config
		Config: config,
	}

	// Set template type booleans
	d.setTemplateTypes(data)

	// Generate package path and theme name
	if err := d.generatePackageInfo(data); err != nil {
		return nil, fmt.Errorf("invalid package configuration: %w", err)
	}

	// Generate color scheme
	themeColor := config.ThemeColor
	if themeColor == "" {
		themeColor = "#6200EE" // Material default
	}
	data.Colors = d.themeProc.GenerateColorScheme(themeColor)

	// Set tab colors directly from config (no auto-processing)
	data.Colors.TabIconColor = d.stripHashPrefix(config.TabIconColorLight)
	data.Colors.ActiveTabColor = d.stripHashPrefix(config.ActiveTabColorLight)
	data.Colors.DarkTabIconColor = d.stripHashPrefix(config.TabIconColorDark)
	data.Colors.DarkActiveTabColor = d.stripHashPrefix(config.ActiveTabColorDark)

	// Set drawer item colors directly from config (matching tab colors pattern)
	data.Colors.DrawerIconColorLight = d.stripHashPrefix(config.DrawerIconColorLight)
	data.Colors.DrawerIconColorDark = d.stripHashPrefix(config.DrawerIconColorDark)
	data.Colors.DrawerActiveColorLight = d.stripHashPrefix(config.DrawerActiveColorLight)
	data.Colors.DrawerActiveColorDark = d.stripHashPrefix(config.DrawerActiveColorDark)

	// Process navigation items based on layout type
	if data.IsDrawer {
		// Use drawer_items for drawer layout (home item now comes from UI)
		if len(config.DrawerItems) > 0 {
			data.NavigationItems = d.navProc.ProcessNavigationItems(config.DrawerItems)
			// Validate and fix navigation items (ensures home item is at index 0)
			data.NavigationItems = d.navProc.ValidateNavigationItems(data.NavigationItems)
		} else {
			// Generate defaults for drawer
			data.NavigationItems = d.navProc.GenerateDefaultNavigationItems("drawer")
		}
	} else if data.IsTabs {
		// Use bottom_tabs for tabs layout
		if len(config.BottomTabs) > 0 {
			data.NavigationItems = d.navProc.ProcessNavigationItems(config.BottomTabs)
		} else {
			// Generate defaults for tabs
			data.NavigationItems = d.navProc.GenerateDefaultNavigationItems("tabs")
		}
	}
	// No else needed - blank/appbar layouts don't use navigation items
	data.NavigationItems = d.navProc.ValidateNavigationItems(data.NavigationItems)

	// Ensure Home tab has the website URL if empty
	for i := range data.NavigationItems {
		if data.NavigationItems[i].IsHome && data.NavigationItems[i].URL == "" {
			data.NavigationItems[i].URL = config.WebsiteURL
		}
	}

	// Process app bar buttons
	if len(config.AppBarButtons) > 0 {
		data.AppBarButtons = d.navProc.ProcessNavigationItems(config.AppBarButtons)
	}

	// Process drawer configuration
	if data.IsDrawer {
		data.Drawer = d.buildDrawerConfig(config, req.ID)

		// Copy drawer images if drawer has logo or background
		hasAnyLogo := data.Drawer.LogoLightPath != "" || data.Drawer.LogoDarkPath != ""
		if hasAnyLogo || data.Drawer.BackgroundImagePath != "" {
			if err := d.assetProc.ProcessDrawerImages(
				data.Drawer.LogoLightPath,
				data.Drawer.LogoDarkPath,
				data.Drawer.BackgroundImagePath,
				projectDir,
			); err != nil {
				d.logger.WithError(err).Warn("Failed to process drawer images")
			}
		}
	}

	// Always create splash config (may be disabled)
	data.Splash = d.buildSplashConfig(config, req.ID)

	// Copy splash images only if splash is actually enabled
	if data.Splash.Enabled && (data.Splash.LogoPath != "" || data.Splash.BackgroundImagePath != "") {
		if err := d.assetProc.ProcessSplashImages(data.Splash.LogoPath, data.Splash.BackgroundImagePath, projectDir); err != nil {
			d.logger.WithError(err).Warn("Failed to process splash images")
		}
	}

	// Download and process icons
	if config.IconZipURL != "" {
		if err := d.processIcons(config.IconZipURL, req.ID, projectDir); err != nil {
			d.logger.WithError(err).Warn("Failed to process icons, using defaults")
		} else {
			data.HasCustomIcons = true
		}
	}

	return data, nil
}

func (d *DataBuilder) setTemplateTypes(data *templateModels.TemplateData) {
	switch data.LayoutTemplate {
	case "blank", "content":
		data.IsBlank = true
	case "app_bar", "appbar":
		data.IsAppBar = true
	case "app_bar_tabs", "tabs":
		data.IsTabs = true
	case "app_bar_drawer", "drawer":
		data.IsDrawer = true
	default:
		// Default to blank
		data.IsBlank = true
		data.LayoutTemplate = "blank"
	}
}

func (d *DataBuilder) generatePackageInfo(data *templateModels.TemplateData) error {
	// Validate package name (app ID) follows Java naming conventions
	if err := d.validatePackageName(data.AppID); err != nil {
		return err
	}

	// Generate package path from app ID
	// e.g., com.example.app -> com/example/app
	data.PackagePath = strings.ReplaceAll(data.AppID, ".", "/")

	// Package name is same as app ID (for package declarations)
	// e.g., com.example.app
	data.PackageName = data.AppID

	// Generate theme name from app name
	// e.g., "My App" -> "MyAppTheme", "OgTattoo.in" -> "OgTattoinTheme"
	// Remove all non-alphanumeric characters to ensure valid Kotlin identifier
	themeName := regexp.MustCompile(`[^a-zA-Z0-9]+`).ReplaceAllString(data.AppName, "")
	if themeName == "" {
		themeName = "App"
	}

	// Validate theme name starts with a letter
	if len(themeName) > 0 && !isLetter(rune(themeName[0])) {
		themeName = "App" + themeName
	}

	data.ThemeName = themeName + "Theme"
	return nil
}

// validatePackageName validates that a package name follows Java/Android naming conventions
// using the shared validation package for consistency across platforms
func (d *DataBuilder) validatePackageName(packageName string) error {
	if err := validation.ValidatePackageName(packageName); err != nil {
		return fmt.Errorf("invalid package configuration: %w", err)
	}

	// Log warning for uppercase letters (existing behavior)
	if validation.HasUppercase(packageName) {
		d.logger.WithField("packageName", packageName).
			Warn("Package name contains uppercase letters - Java convention is lowercase")
	}

	return nil
}

// isLetter checks if a rune is a letter
func isLetter(r rune) bool {
	return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
}

func (d *DataBuilder) mapOrientation(orientation string) string {
	switch orientation {
	case "portrait":
		return "portrait"
	case "landscape":
		return "landscape"
	case "auto", "system":
		return "unspecified"
	default:
		return "portrait"
	}
}

func (d *DataBuilder) mapProgressIndicator(indicator string) string {
	switch indicator {
	case "linear":
		return "linear"
	case "circular":
		return "circular"
	case "disable", "none":
		return "none"
	default:
		return "linear"
	}
}

func (d *DataBuilder) buildDrawerConfig(config models.BuildConfig, buildID string) templateModels.DrawerConfig {
	drawer := templateModels.DrawerConfig{
		Enabled:       true,
		Mode:          config.DrawerMode,
		Title:         config.DrawerTitle,
		Subtitle:      config.DrawerSubtitle,
		TextColorMode: config.DrawerTextColorMode,
		LogoEnabled:   config.DrawerLogoEnabled,
		LogoSize:      config.DrawerLogoSize,
	}

	// Validate and set default logo size
	if drawer.LogoSize == 0 || drawer.LogoSize < 40 || drawer.LogoSize > 200 {
		drawer.LogoSize = 120 // default size
	}

	// Process background (strip hash for Compose Color format)
	if config.DrawerBackgroundColor != "" {
		drawer.BackgroundColor = d.stripHashPrefix(config.DrawerBackgroundColor)
	}

	// Download background image if provided
	if config.DrawerBackgroundImage != "" {
		imageURL := d.normalizeAssetURL(config.DrawerBackgroundImage)
		imagePath, err := d.assetProc.DownloadImage(imageURL, buildID, "drawer_bg")
		if err == nil {
			drawer.HasBackgroundImage = true
			drawer.BackgroundImagePath = imagePath
		}
	}

	// Download logos if enabled AND provided
	if config.DrawerLogoEnabled {
		// Download light mode logo
		if config.DrawerLogoLight != "" {
			logoURL := d.normalizeAssetURL(config.DrawerLogoLight)
			logoPath, err := d.assetProc.DownloadImage(logoURL, buildID, "drawer_logo_light")
			if err == nil {
				drawer.HasLogoLight = true
				drawer.LogoLightPath = logoPath
			}
		}

		// Download dark mode logo
		if config.DrawerLogoDark != "" {
			logoURL := d.normalizeAssetURL(config.DrawerLogoDark)
			logoPath, err := d.assetProc.DownloadImage(logoURL, buildID, "drawer_logo_dark")
			if err == nil {
				drawer.HasLogoDark = true
				drawer.LogoDarkPath = logoPath
			}
		}

		// Backward compatibility: if new fields are empty but old logo exists, use it for both
		if !drawer.HasLogoLight && !drawer.HasLogoDark && config.DrawerLogo != "" {
			logoURL := d.normalizeAssetURL(config.DrawerLogo)
			// Download as light logo
			logoLightPath, err := d.assetProc.DownloadImage(logoURL, buildID, "drawer_logo_light")
			if err == nil {
				drawer.HasLogoLight = true
				drawer.LogoLightPath = logoLightPath
			}
			// Also download as dark logo
			logoDarkPath, err := d.assetProc.DownloadImage(logoURL, buildID, "drawer_logo_dark")
			if err == nil {
				drawer.HasLogoDark = true
				drawer.LogoDarkPath = logoDarkPath
			}
		}
	}

	// Determine text color based on mode
	if drawer.TextColorMode == "dark" {
		drawer.TextColor = "000000"
	} else {
		drawer.TextColor = "FFFFFF"
	}

	// Compute adaptive scrim for background images
	// Light text (white) needs dark scrim for contrast
	// Dark text (black) needs light scrim for contrast
	if drawer.TextColorMode == "dark" {
		drawer.ScrimColor = "FFFFFF" // White scrim for dark text
		drawer.ScrimOpacity = "0.7"  // 70% opacity
	} else {
		drawer.ScrimColor = "000000" // Black scrim for light text
		drawer.ScrimOpacity = "0.4"  // 40% opacity
	}

	return drawer
}

func (d *DataBuilder) buildSplashConfig(config models.BuildConfig, buildID string) templateModels.SplashConfig {
	// Determine if splash is actually enabled based on content
	enabled := config.SplashLogoEnabled ||
		config.SplashTitle != "" ||
		config.SplashSubtitle != "" ||
		config.SplashAnimation != ""

	splash := templateModels.SplashConfig{
		Enabled:        enabled,
		BackgroundType: config.SplashBackgroundType,
		Title:          config.SplashTitle,
		Subtitle:       config.SplashSubtitle,
		TextColorMode:  config.SplashTextColorMode,
		LogoEnabled:    config.SplashLogoEnabled,
		LogoSize:       config.SplashLogoSize,
		Duration:       config.SplashDelay,
		Animation:      d.getSplashAnimation(config),
	}

	// Validate and set default logo size
	if splash.LogoSize == 0 || splash.LogoSize < 60 || splash.LogoSize > 240 {
		splash.LogoSize = 160 // default size
	}

	// Default duration
	if splash.Duration <= 0 {
		splash.Duration = 3
	}

	// Cap maximum duration to 10 seconds for better UX
	if splash.Duration > 10 {
		d.logger.WithField("requested_duration", splash.Duration).
			Warn("Splash duration exceeds maximum of 10 seconds, capping to 10s")
		splash.Duration = 10
	}

	// Process background with defensive hex color detection
	if config.SplashBackgroundType == "color" || strings.HasPrefix(config.SplashBackground, "#") {
		// It's a color - don't try to download it
		splash.BackgroundColor = config.SplashBackground
		// Set default if empty
		if splash.BackgroundColor == "" || splash.BackgroundColor == "#" {
			splash.BackgroundColor = "#FFFFFF"
		}
	} else if config.SplashBackgroundType == "image" && config.SplashBackground != "" {
		// It's an image URL - download it
		imageURL := d.normalizeAssetURL(config.SplashBackground)
		imagePath, err := d.assetProc.DownloadImage(imageURL, buildID, "splash_bg")
		if err == nil {
			splash.HasBackgroundImage = true
			splash.BackgroundImagePath = imagePath
		}
	}

	// Ensure BackgroundColor always has a default value
	if splash.BackgroundColor == "" {
		splash.BackgroundColor = "#FFFFFF"
	}

	// Download logo if provided
	if config.SplashLogo != "" {
		logoURL := d.normalizeAssetURL(config.SplashLogo)
		logoPath, err := d.assetProc.DownloadImage(logoURL, buildID, "splash_logo")
		if err == nil {
			splash.HasLogo = true
			splash.LogoPath = logoPath
		}
	}

	// Determine text color
	if splash.TextColorMode == "dark" {
		splash.TextColor = "000000"
	} else {
		splash.TextColor = "FFFFFF"
	}

	return splash
}

func (d *DataBuilder) processIcons(iconZipURL, buildID, projectDir string) error {
	// Download icon ZIP (normalize URL)
	zipURL := d.normalizeAssetURL(iconZipURL)
	zipPath := filepath.Join(d.assetProc.cacheDir, "icons", fmt.Sprintf("%s_icons.zip", buildID))
	if err := d.assetProc.DownloadFile(zipURL, zipPath); err != nil {
		return fmt.Errorf("failed to download icon ZIP: %w", err)
	}

	// Extract ZIP
	extractDir := filepath.Join(d.assetProc.cacheDir, "icons", buildID)
	if err := d.assetProc.ExtractIconZip(zipPath, extractDir); err != nil {
		return fmt.Errorf("failed to extract icon ZIP: %w", err)
	}

	// Process and copy icons to project
	if err := d.assetProc.ProcessIconsForAndroid(extractDir, projectDir); err != nil {
		return fmt.Errorf("failed to process icons: %w", err)
	}

	// Cleanup
	defer d.assetProc.Cleanup(zipPath, extractDir)

	return nil
}

// getPullToRefreshEnabled returns pull-to-refresh setting
// Appy platform sends explicit boolean value with platform-specific defaults
func (d *DataBuilder) getPullToRefreshEnabled(config models.BuildConfig) bool {
	return config.PullToRefresh
}

// getAndroidVersionName returns Android-specific version name or falls back to generic
func (d *DataBuilder) getAndroidVersionName(config models.BuildConfig) string {
	if config.AndroidVersionName != "" {
		return config.AndroidVersionName
	}
	return "1.0.0" // Default fallback
}

// getVersionCode returns version code from config or defaults to 1
func (d *DataBuilder) getVersionCode(config models.BuildConfig) int {
	if config.VersionCode > 0 {
		return config.VersionCode
	}
	return 1 // Default fallback for safety
}

// getBuildFormat returns build format with default to apk
func (d *DataBuilder) getBuildFormat(config models.BuildConfig) string {
	if config.BuildFormat == "aab" {
		return "aab"
	}
	return "apk" // Default to apk
}

// getSplashAnimation returns splash animation type with default fade
func (d *DataBuilder) getSplashAnimation(config models.BuildConfig) string {
	animation := config.SplashAnimation
	switch animation {
	case "fade", "slide", "zoom", "none":
		return animation
	default:
		return "fade" // Default animation
	}
}

// stripHashPrefix removes # prefix from hex color if present
func (d *DataBuilder) stripHashPrefix(hexColor string) string {
	if len(hexColor) > 0 && hexColor[0] == '#' {
		return hexColor[1:]
	}
	return hexColor
}

// isLightColor determines if a hex color is light (high luminance)
// Used to decide if status bar icons should be light or dark
func (d *DataBuilder) isLightColor(hexColor string) bool {
	hex := d.stripHashPrefix(hexColor)
	if len(hex) < 6 {
		return false // Default to dark if invalid
	}

	// Parse RGB values
	r, g, b := 0, 0, 0
	fmt.Sscanf(hex[:2], "%02x", &r)
	fmt.Sscanf(hex[2:4], "%02x", &g)
	fmt.Sscanf(hex[4:6], "%02x", &b)

	// Calculate relative luminance using sRGB
	// Formula: 0.2126 * R + 0.7152 * G + 0.0722 * B
	luminance := 0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b)

	// If luminance > 128 (half of 255), consider it light
	return luminance > 128
}
