package android

import (
	"fmt"
	"os"
	"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/platforms/android-webview/engine"
	"github.com/titansys/appy-builder/internal/platforms/android-webview/processors"
	"github.com/titansys/appy-builder/internal/platforms/android-webview/tmpl"
)

// Customizer modifies template files using template engine
type Customizer struct {
	logger      *logrus.Logger
	engine      *engine.Engine
	dataBuilder *processors.DataBuilder
}

// NewCustomizer creates a new template customizer with template engine
func NewCustomizer(logger *logrus.Logger, cacheDir string, appyBaseURL string) *Customizer {
	return &Customizer{
		logger:      logger,
		engine:      engine.NewEngine(logger, tmpl.Templates),
		dataBuilder: processors.NewDataBuilder(logger, cacheDir, appyBaseURL),
	}
}

// Customize applies all customizations to the template
func (c *Customizer) Customize(templateDir string, req models.BuildRequest) error {
	// Find the base template directory
	baseDir, err := c.findBaseDir(templateDir)
	if err != nil {
		return fmt.Errorf("failed to find base directory: %w", err)
	}

	c.logger.WithFields(logrus.Fields{
		"build_id":        req.ID,
		"template_dir":    templateDir,
		"base_dir":        baseDir,
		"layout_template": req.Config.LayoutTemplate,
	}).Info("Starting template customization")

	// Build template data
	data, err := c.dataBuilder.BuildTemplateData(req, baseDir)
	if err != nil {
		return fmt.Errorf("failed to build template data: %w", err)
	}

	// Apply customizations
	steps := []struct {
		name string
		fn   func(string, interface{}) error
	}{
		{"app ID and name", c.updateBuildGradle},
		{"strings.xml", c.updateStrings},
		{"themes.xml", c.updateThemesXml},
		{"themes-v29.xml", c.updateThemesV29Xml},
		{"AndroidManifest.xml", c.updateManifest},
		{"Color.kt", c.updateColors},
		{"Theme.kt", c.updateTheme},
		{"NetworkErrorOverlay.kt", c.updateNetworkErrorOverlay},
		{"WebView.kt", c.updateWebView},
		{"SplashScreen.kt", c.updateSplashScreen},
		{"MainActivity.kt", c.updateMainActivity},
		{"NetworkMonitor.kt", c.updateNetworkMonitor},
		{"old package cleanup", c.deleteOldPackageDir},
		{"PermissionBridge.kt", c.updatePermissionBridge},
		{"Firebase setup", c.setupFirebase},
		{"FileProvider paths", c.setupFileProviderPaths},
		{"Location services", c.setupLocationServices},
	}

	for _, step := range steps {
		if err := step.fn(baseDir, data); err != nil {
			return fmt.Errorf("failed to update %s: %w", step.name, err)
		}
	}

	c.logger.WithField("build_id", req.ID).Info("Template customization completed successfully")
	return nil
}

// findBaseDir locates the base Android project directory
func (c *Customizer) findBaseDir(templateDir string) (string, error) {
	// Check if templateDir itself contains app/build.gradle.kts
	if _, err := os.Stat(filepath.Join(templateDir, "app", "build.gradle.kts")); err == nil {
		return templateDir, nil
	}

	// Otherwise, look in subdirectories
	entries, err := os.ReadDir(templateDir)
	if err != nil {
		return "", err
	}

	for _, entry := range entries {
		if entry.IsDir() {
			subdir := filepath.Join(templateDir, entry.Name())
			if _, err := os.Stat(filepath.Join(subdir, "app", "build.gradle.kts")); err == nil {
				return subdir, nil
			}
		}
	}

	return "", fmt.Errorf("could not find Android project in template")
}

// updateBuildGradle updates app/build.gradle.kts
func (c *Customizer) updateBuildGradle(baseDir string, data interface{}) error {
	// For now, use regex-based replacement (build.gradle.kts is complex)
	// We can template this later if needed
	buildGradlePath := filepath.Join(baseDir, "app", "build.gradle.kts")

	content, err := os.ReadFile(buildGradlePath)
	if err != nil {
		return err
	}

	templateData := data.(*templateModels.TemplateData)

	// Replace applicationId
	newContent := string(content)
	newContent = replaceRegex(newContent, `applicationId\s*=\s*"[^"]*"`,
		fmt.Sprintf(`applicationId = "%s"`, templateData.AppID))

	// Replace namespace
	newContent = replaceRegex(newContent, `namespace\s*=\s*"[^"]*"`,
		fmt.Sprintf(`namespace = "%s"`, templateData.AppID))

	// Replace versionCode
	newContent = replaceRegex(newContent, `versionCode\s*=\s*\d+`,
		fmt.Sprintf(`versionCode = %d`, templateData.VersionCode))

	// Replace versionName - use AndroidVersionName if available
	versionName := templateData.AndroidVersionName
	if versionName == "" {
		versionName = templateData.VersionName
	}
	newContent = replaceRegex(newContent, `versionName\s*=\s*"[^"]*"`,
		fmt.Sprintf(`versionName = "%s"`, versionName))

	return os.WriteFile(buildGradlePath, []byte(newContent), 0644)
}

// updateStrings updates app/src/main/res/values/strings.xml
func (c *Customizer) updateStrings(baseDir string, data interface{}) error {
	stringsPath := filepath.Join(baseDir, "app", "src", "main", "res", "values", "strings.xml")

	content, err := os.ReadFile(stringsPath)
	if err != nil {
		return err
	}

	templateData := data.(*templateModels.TemplateData)

	// Replace app_name
	newContent := replaceRegex(string(content), `<string name="app_name">[^<]*</string>`,
		fmt.Sprintf(`<string name="app_name">%s</string>`, escapeXML(templateData.AppName)))

	return os.WriteFile(stringsPath, []byte(newContent), 0644)
}

// updateThemesXml creates themes.xml with edge-to-edge configuration (API 24-28)
func (c *Customizer) updateThemesXml(baseDir string, data interface{}) error {
	themesXmlPath := filepath.Join(baseDir, "app", "src", "main", "res", "values", "themes.xml")
	return c.engine.RenderFile("themes.xml.tmpl", data, themesXmlPath)
}

// updateThemesV29Xml creates themes.xml for API 29+ with enforceContrast attributes
func (c *Customizer) updateThemesV29Xml(baseDir string, data interface{}) error {
	// Ensure values-v29 directory exists
	v29Dir := filepath.Join(baseDir, "app", "src", "main", "res", "values-v29")
	if err := os.MkdirAll(v29Dir, 0755); err != nil {
		return fmt.Errorf("failed to create values-v29 directory: %w", err)
	}

	themesV29XmlPath := filepath.Join(v29Dir, "themes.xml")
	return c.engine.RenderFile("themes-v29.xml.tmpl", data, themesV29XmlPath)
}

// updateManifest updates AndroidManifest.xml using template
func (c *Customizer) updateManifest(baseDir string, data interface{}) error {
	manifestPath := filepath.Join(baseDir, "app", "src", "main", "AndroidManifest.xml")
	return c.engine.RenderFile("AndroidManifest.xml.tmpl", data, manifestPath)
}

// updateColors updates Color.kt using template
func (c *Customizer) updateColors(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	colorPath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "ui", "theme", "Color.kt")
	return c.engine.RenderFile("Color.kt.tmpl", data, colorPath)
}

// updateTheme updates Theme.kt using template
func (c *Customizer) updateTheme(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	themePath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "ui", "theme", "Theme.kt")
	return c.engine.RenderFile("Theme.kt.tmpl", data, themePath)
}

// updateNetworkErrorOverlay updates NetworkErrorOverlay.kt using template
func (c *Customizer) updateNetworkErrorOverlay(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	overlayPath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "ui", "components", "NetworkErrorOverlay.kt")
	return c.engine.RenderFile("NetworkErrorOverlay.kt.tmpl", data, overlayPath)
}

// updateWebView updates WebView.kt using template
func (c *Customizer) updateWebView(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	webViewPath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "ui", "components", "WebView.kt")
	return c.engine.RenderFile("WebView.kt.tmpl", data, webViewPath)
}

// updateSplashScreen updates SplashScreen.kt using template
func (c *Customizer) updateSplashScreen(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)

	// Only render splash screen if enabled
	if !templateData.Splash.Enabled {
		c.logger.Debug("Splash screen not enabled, skipping")
		return nil
	}

	splashPath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "ui", "components", "SplashScreen.kt")
	return c.engine.RenderFile("SplashScreen.kt.tmpl", data, splashPath)
}

// updateMainActivity updates MainActivity.kt using template
func (c *Customizer) updateMainActivity(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	mainActivityPath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "MainActivity.kt")
	return c.engine.RenderFile("MainActivity.kt.tmpl", data, mainActivityPath)
}

// updateNetworkMonitor updates NetworkMonitor.kt using template
func (c *Customizer) updateNetworkMonitor(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	networkMonitorPath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "utils", "NetworkMonitor.kt")
	return c.engine.RenderFile("NetworkMonitor.kt.tmpl", data, networkMonitorPath)
}

// updatePermissionBridge updates PermissionBridge.kt using template
func (c *Customizer) updatePermissionBridge(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)
	permissionBridgePath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "PermissionBridge.kt")
	return c.engine.RenderFile("PermissionBridge.kt.tmpl", data, permissionBridgePath)
}

// Helper functions

func replaceRegex(content, pattern, replacement string) string {
	re := regexp.MustCompile(pattern)
	return re.ReplaceAllString(content, replacement)
}

func escapeXML(s string) string {
	s = replace(s, "&", "&amp;")
	s = replace(s, "<", "&lt;")
	s = replace(s, ">", "&gt;")
	s = replace(s, "\"", "&quot;")
	s = replace(s, "'", "&apos;")
	return s
}

func replace(s, old, new string) string {
	return strings.ReplaceAll(s, old, new)
}

// setupFirebase configures Firebase/FCM if enabled
func (c *Customizer) setupFirebase(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)

	// Skip if Firebase is not enabled
	if !templateData.FirebaseEnabled {
		c.logger.Debug("Firebase not enabled, skipping FCM setup")
		return nil
	}

	c.logger.Info("Setting up Firebase/FCM")

	// 1. Write google-services.json to app directory
	if templateData.GoogleServicesJSON != "" {
		googleServicesPath := filepath.Join(baseDir, "app", "google-services.json")
		if err := os.WriteFile(googleServicesPath, []byte(templateData.GoogleServicesJSON), 0644); err != nil {
			return fmt.Errorf("failed to write google-services.json: %w", err)
		}
		c.logger.WithField("path", googleServicesPath).Debug("Wrote google-services.json")
	} else {
		return fmt.Errorf("Firebase enabled but google-services.json content is empty")
	}

	// 2. Update root build.gradle.kts to add Google services plugin
	rootBuildGradle := filepath.Join(baseDir, "build.gradle.kts")
	rootContent, err := os.ReadFile(rootBuildGradle)
	if err != nil {
		return fmt.Errorf("failed to read root build.gradle.kts: %w", err)
	}

	rootStr := string(rootContent)
	// Add Google services plugin after compose plugin
	if !strings.Contains(rootStr, "com.google.gms.google-services") {
		rootStr = replaceRegex(rootStr,
			`id\("org\.jetbrains\.kotlin\.plugin\.compose"\) version "[^"]*" apply false\n\}`,
			`id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false
    id("com.google.gms.google-services") version "4.4.2" apply false
}`)
		if err := os.WriteFile(rootBuildGradle, []byte(rootStr), 0644); err != nil {
			return fmt.Errorf("failed to update root build.gradle.kts: %w", err)
		}
	}

	// 3. Update app/build.gradle.kts to add Firebase dependencies
	appBuildGradle := filepath.Join(baseDir, "app", "build.gradle.kts")
	appContent, err := os.ReadFile(appBuildGradle)
	if err != nil {
		return fmt.Errorf("failed to read app build.gradle.kts: %w", err)
	}

	appStr := string(appContent)

	// Add Google services plugin in app module
	if !strings.Contains(appStr, "com.google.gms.google-services") {
		appStr = replaceRegex(appStr,
			`id\("org\.jetbrains\.kotlin\.plugin\.compose"\)\n\}`,
			`id("org.jetbrains.kotlin.plugin.compose")
    id("com.google.gms.google-services")
}`)
	}

	// Add Firebase dependencies
	if !strings.Contains(appStr, "firebase-bom") {
		// Find dependencies block and add Firebase
		firebaseDeps := `
    // Firebase
    implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
    implementation("com.google.firebase:firebase-messaging-ktx")

    // Debug`
		appStr = strings.Replace(appStr, "\n    // Debug", firebaseDeps, 1)
	}

	if err := os.WriteFile(appBuildGradle, []byte(appStr), 0644); err != nil {
		return fmt.Errorf("failed to update app build.gradle.kts: %w", err)
	}

	// 4. Create FirebaseMessagingService.kt
	servicePath := filepath.Join(baseDir, "app", "src", "main", "java", templateData.PackagePath, "services", "MyFirebaseMessagingService.kt")

	// Ensure services directory exists
	servicesDir := filepath.Dir(servicePath)
	if err := os.MkdirAll(servicesDir, 0755); err != nil {
		return fmt.Errorf("failed to create services directory: %w", err)
	}

	// Write the FirebaseMessagingService
	serviceContent := fmt.Sprintf(`package %s.services

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import %s.MainActivity
import %s.R
import java.net.URL

class MyFirebaseMessagingService : FirebaseMessagingService() {

    companion object {
        private const val TAG = "FCMService"
        private const val CHANNEL_ID = "default_notification_channel"
        private const val CHANNEL_NAME = "Notifications"
    }

    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")
        // Send token to your server if needed
    }

    override fun onMessageReceived(message: RemoteMessage) {
        Log.d(TAG, "From: ${message.from}")

        // Check for notification payload
        message.notification?.let { notification ->
            val title = notification.title ?: ""
            val body = notification.body ?: ""
            val imageUrl = notification.imageUrl?.toString()

            sendNotification(title, body, imageUrl)
        }

        // Check for data payload
        if (message.data.isNotEmpty()) {
            Log.d(TAG, "Message data payload: ${message.data}")

            val title = message.data["title"] ?: ""
            val body = message.data["body"] ?: ""
            val imageUrl = message.data["image"]

            if (title.isNotEmpty() || body.isNotEmpty()) {
                sendNotification(title, body, imageUrl)
            }
        }
    }

    private fun sendNotification(title: String, messageBody: String, imageUrl: String?) {
        val intent = Intent(this, MainActivity::class.java).apply {
            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        }

        val pendingIntent = PendingIntent.getActivity(
            this, 0, intent,
            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
        )

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(title)
            .setContentText(messageBody)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)
            .setPriority(NotificationCompat.PRIORITY_HIGH)

        // Add image if provided
        if (!imageUrl.isNullOrEmpty()) {
            try {
                val url = URL(imageUrl)
                val bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream())
                notificationBuilder.setLargeIcon(bitmap)
                    .setStyle(NotificationCompat.BigPictureStyle()
                        .bigPicture(bitmap)
                        .bigLargeIcon(null as android.graphics.Bitmap?))
            } catch (e: Exception) {
                Log.e(TAG, "Failed to load notification image", e)
            }
        }

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Create notification channel for Android O+
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_HIGH
            ).apply {
                description = "App notifications"
            }
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(System.currentTimeMillis().toInt(), notificationBuilder.build())
    }
}
`, templateData.PackageName, templateData.PackageName, templateData.PackageName)

	if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil {
		return fmt.Errorf("failed to write FirebaseMessagingService: %w", err)
	}

	c.logger.Info("Firebase/FCM setup completed successfully")
	return nil
}

// deleteOldPackageDir removes the old template package directory to prevent conflicts
func (c *Customizer) deleteOldPackageDir(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)

	// Map layout templates to their old package names
	oldPackageMap := map[string]string{
		"blank":          "com/sample/content/kotlin",
		"app_bar":        "com/sample/appbar/kotlin",
		"appbar":         "com/sample/appbar/kotlin",
		"app_bar_tabs":   "com/sample/tabs/kotlin",
		"tabs":           "com/sample/tabs/kotlin",
		"app_bar_drawer": "com/sample/drawer/kotlin",
		"drawer":         "com/sample/drawer/kotlin",
	}

	// Get old package path for this template
	oldPackage, ok := oldPackageMap[templateData.LayoutTemplate]
	if !ok {
		c.logger.WithField("layout_template", templateData.LayoutTemplate).Warn("Unknown template type, skipping old package cleanup")
		return nil
	}

	// Build path to old package directory
	oldPackagePath := filepath.Join(baseDir, "app", "src", "main", "java", oldPackage)

	// Check if old package directory exists
	if _, err := os.Stat(oldPackagePath); os.IsNotExist(err) {
		c.logger.WithField("old_package_path", oldPackagePath).Debug("Old package directory does not exist, skipping cleanup")
		return nil
	}

	// Delete old package directory
	if err := os.RemoveAll(oldPackagePath); err != nil {
		return fmt.Errorf("failed to delete old package directory %s: %w", oldPackagePath, err)
	}

	c.logger.WithField("old_package_path", oldPackagePath).Debug("Deleted old package directory")
	return nil
}

// setupFileProviderPaths creates the file_paths.xml for FileProvider (needed for camera)
func (c *Customizer) setupFileProviderPaths(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)

	// Skip if camera permission is not enabled
	if !templateData.Permissions.Camera {
		c.logger.Debug("Camera permission not enabled, skipping FileProvider paths setup")
		return nil
	}

	c.logger.Info("Setting up FileProvider paths for camera")

	// Create res/xml directory if it doesn't exist
	xmlDir := filepath.Join(baseDir, "app", "src", "main", "res", "xml")
	if err := os.MkdirAll(xmlDir, 0755); err != nil {
		return fmt.Errorf("failed to create xml directory: %w", err)
	}

	// Write file_paths.xml
	filePathsContent := `<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path name="cache" path="." />
    <files-path name="files" path="." />
    <external-files-path name="external_files" path="." />
</paths>
`
	filePathsPath := filepath.Join(xmlDir, "file_paths.xml")
	if err := os.WriteFile(filePathsPath, []byte(filePathsContent), 0644); err != nil {
		return fmt.Errorf("failed to write file_paths.xml: %w", err)
	}

	c.logger.WithField("path", filePathsPath).Debug("Created file_paths.xml")
	return nil
}

// setupLocationServices adds Google Play Services Location dependency
func (c *Customizer) setupLocationServices(baseDir string, data interface{}) error {
	templateData := data.(*templateModels.TemplateData)

	// Skip if location permission is not enabled
	if !templateData.Permissions.Location {
		c.logger.Debug("Location permission not enabled, skipping location services setup")
		return nil
	}

	c.logger.Info("Setting up Google Play Services Location")

	// Update app/build.gradle.kts to add location dependency
	appBuildGradle := filepath.Join(baseDir, "app", "build.gradle.kts")
	appContent, err := os.ReadFile(appBuildGradle)
	if err != nil {
		return fmt.Errorf("failed to read app build.gradle.kts: %w", err)
	}

	appStr := string(appContent)

	// Add location dependency if not present
	if !strings.Contains(appStr, "play-services-location") {
		// Find dependencies block and add location
		locationDep := `
    // Google Play Services Location
    implementation("com.google.android.gms:play-services-location:21.0.1")

    // Debug`
		appStr = strings.Replace(appStr, "\n    // Debug", locationDep, 1)

		if err := os.WriteFile(appBuildGradle, []byte(appStr), 0644); err != nil {
			return fmt.Errorf("failed to update app build.gradle.kts with location dependency: %w", err)
		}
	}

	c.logger.Info("Google Play Services Location setup completed")
	return nil
}
