package android

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/sirupsen/logrus"

	"github.com/titansys/appy-builder/internal/config"
	"github.com/titansys/appy-builder/internal/models"
	"github.com/titansys/appy-builder/internal/queue"
)

// Builder handles the complete build process
type Builder struct {
	config     *config.Config
	fetcher    *Fetcher
	customizer *Customizer
	logger     *logrus.Logger
	appyClient interface {
		DownloadKeystore(keystoreID string) ([]byte, error)
		DownloadKeystoreDynamic(keystoreID, siteURL, authKey string) ([]byte, error)
	}
}

// NewBuilder creates a new Android builder
func NewBuilder(cfg *config.Config, logger *logrus.Logger) *Builder {
	cacheDir := "./storage/cache"
	os.MkdirAll(cacheDir, 0755)

	return &Builder{
		config:     cfg,
		fetcher:    NewFetcher(),
		customizer: NewCustomizer(logger, cacheDir, cfg.SiteURL),
		logger:     logger,
	}
}

// SetAppyClient sets the Appy client for downloading keystores
func (b *Builder) SetAppyClient(client interface {
	DownloadKeystore(keystoreID string) ([]byte, error)
	DownloadKeystoreDynamic(keystoreID, siteURL, authKey string) ([]byte, error)
}) {
	b.appyClient = client
}

// sanitizeLogs removes the working directory from log paths for privacy
func (b *Builder) sanitizeLogs(logs string) string {
	if logs == "" {
		return logs
	}

	cwd, err := os.Getwd()
	if err != nil {
		return logs
	}

	// Handle file:// URLs and regular paths
	sanitized := strings.ReplaceAll(logs, "file://"+cwd+"/", "file:///")
	sanitized = strings.ReplaceAll(sanitized, cwd+"/", "/")
	sanitized = strings.ReplaceAll(sanitized, cwd, "")

	return sanitized
}

// Build performs the complete build process
func (b *Builder) Build(req models.BuildRequest) (string, error) {
	finalPath, _, err := b.BuildWithLogs(req)
	return finalPath, err
}

// BuildWithLogs performs the complete build process and returns build logs
func (b *Builder) BuildWithLogs(req models.BuildRequest) (string, string, error) {
	// Use composite key for multi-tenant isolation in hosted mode
	compositeKey := queue.BuildCompositeKey(req.PurchaseCode, req.ID)

	b.logger.WithFields(logrus.Fields{
		"build_id":      req.ID,
		"composite_key": compositeKey,
		"app_name":      req.Config.AppName,
	}).Info("Starting build process")

	var buildLogs string

	// 1. Fetch template (use composite key for workspace isolation)
	b.logger.WithFields(logrus.Fields{
		"build_id":        req.ID,
		"composite_key":   compositeKey,
		"platform":        req.Platform,
		"layout_template": req.Config.LayoutTemplate,
	}).Debug("Fetching template")
	templateDir, err := b.fetcher.FetchTemplate(req.Platform, req.Config.LayoutTemplate, compositeKey)
	if err != nil {
		return "", "", fmt.Errorf("template fetch failed: %w", err)
	}

	// Cleanup workspace after build (skip in debug mode)
	if !b.config.Debug {
		defer b.cleanup(templateDir)
	} else {
		b.logger.WithField("workspace", templateDir).Debug("Debug mode: workspace will be preserved for inspection")
	}

	// 2. Customize template
	b.logger.WithFields(logrus.Fields{
		"build_id": req.ID,
	}).Debug("Customizing template")

	if err := b.customizer.Customize(templateDir, req); err != nil {
		return "", "", fmt.Errorf("template customization failed: %w", err)
	}

	// 3. Handle signing for release builds
	if req.BuildType == "release" && req.Config.KeystoreFile != "" && b.appyClient != nil {
		b.logger.WithFields(logrus.Fields{
			"build_id": req.ID,
		}).Debug("Downloading keystore for release build")
		if err := b.setupKeystoreSigning(templateDir, req.Config, req.SiteURL, req.PurchaseCode); err != nil {
			return "", "", fmt.Errorf("failed to setup signing: %w", err)
		}
	}

	// 4. Build artifact (APK or AAB)
	buildFormat := req.Config.BuildFormat
	if buildFormat == "" {
		buildFormat = "apk" // Default to APK
	}

	b.logger.WithFields(logrus.Fields{
		"build_id":   req.ID,
		"build_type": req.BuildType,
		"format":     buildFormat,
	}).Info("Building artifact")

	artifactPath, logFile, err := b.buildArtifact(templateDir, req.BuildType, buildFormat)
	if err != nil {
		// Read logs even on failure
		if logFile != "" {
			if logContent, readErr := os.ReadFile(logFile); readErr == nil {
				buildLogs = b.sanitizeLogs(string(logContent))
			}
		}
		return "", buildLogs, fmt.Errorf("build failed: %w", err)
	}

	// Read build logs
	if logFile != "" {
		if logContent, readErr := os.ReadFile(logFile); readErr == nil {
			buildLogs = b.sanitizeLogs(string(logContent))
		}
	}

	// 5. Copy artifact to builds directory (use composite key for file isolation)
	finalPath, err := b.copyArtifact(artifactPath, compositeKey, buildFormat)
	if err != nil {
		return "", buildLogs, fmt.Errorf("failed to copy artifact: %w", err)
	}

	b.logger.WithFields(logrus.Fields{
		"build_id":      req.ID,
		"composite_key": compositeKey,
	}).Info("Build completed successfully")
	return finalPath, buildLogs, nil
}

// buildArtifact executes Gradle build and returns artifact path and log file path
func (b *Builder) buildArtifact(templateDir, buildType, buildFormat string) (string, string, error) {
	// Find the base project directory
	baseDir, err := b.findBaseDir(templateDir)
	if err != nil {
		return "", "", err
	}

	// Determine build task based on type and format
	task := getGradleTask(buildType, buildFormat)

	// Make gradlew executable
	gradlewPath := filepath.Join(baseDir, "gradlew")

	// Convert to absolute path for exec.Command compatibility
	absGradlewPath, err := filepath.Abs(gradlewPath)
	if err != nil {
		return "", "", fmt.Errorf("failed to get absolute path for gradlew: %w", err)
	}

	if err := os.Chmod(absGradlewPath, 0755); err != nil {
		return "", "", fmt.Errorf("failed to make gradlew executable: %w", err)
	}

	// Set up environment
	env := os.Environ()
	if androidHome := os.Getenv("ANDROID_HOME"); androidHome != "" {
		env = append(env, fmt.Sprintf("ANDROID_HOME=%s", androidHome))
	}

	// Create log file
	// Storage is hardcoded to ./storage
	logDir := "./storage/logs"
	os.MkdirAll(logDir, 0755)
	logFile := filepath.Join(logDir, fmt.Sprintf("build_%s.log", time.Now().Format("20060102_150405")))
	logWriter, err := os.Create(logFile)
	if err != nil {
		return "", "", fmt.Errorf("failed to create log file: %w", err)
	}
	defer logWriter.Close()

	// Execute Gradle build
	cmd := exec.Command(absGradlewPath, task)
	cmd.Dir = baseDir
	cmd.Env = env
	cmd.Stdout = logWriter
	cmd.Stderr = logWriter

	b.logger.WithFields(logrus.Fields{
		"command": fmt.Sprintf("%s %s", absGradlewPath, task),
		"dir":     baseDir,
	}).Debug("Executing Gradle build")

	// Run with timeout
	done := make(chan error, 1)
	go func() {
		done <- cmd.Run()
	}()

	select {
	case err := <-done:
		if err != nil {
			return "", logFile, fmt.Errorf("gradle build failed (see %s): %w", logFile, err)
		}
	case <-time.After(time.Duration(b.config.BuildTimeout) * time.Second):
		cmd.Process.Kill()
		return "", logFile, fmt.Errorf("build timeout after %d seconds", b.config.BuildTimeout)
	}

	// Find the generated artifact (APK or AAB)
	artifactPath, err := b.findArtifact(baseDir, buildType, buildFormat)
	if err != nil {
		return "", logFile, fmt.Errorf("artifact not found after build: %w", err)
	}

	b.logger.WithFields(logrus.Fields{
		"path":   artifactPath,
		"format": buildFormat,
	}).Debug("Artifact generated")
	return artifactPath, logFile, nil
}

// findArtifact locates the generated APK or AAB file
func (b *Builder) findArtifact(baseDir, buildType, buildFormat string) (string, error) {
	var searchPath string
	var outputDir string
	var fileExt string

	if buildFormat == "aab" {
		// AAB files are in bundle output directory
		outputDir = filepath.Join(baseDir, "app", "build", "outputs", "bundle")
		fileExt = ".aab"
		if buildType == "release" {
			searchPath = filepath.Join(outputDir, "release", "app-release.aab")
		} else {
			searchPath = filepath.Join(outputDir, "debug", "app-debug.aab")
		}
	} else {
		// APK files are in apk output directory
		outputDir = filepath.Join(baseDir, "app", "build", "outputs", "apk")
		fileExt = ".apk"
		if buildType == "release" {
			searchPath = filepath.Join(outputDir, "release", "app-release.apk")
		} else {
			searchPath = filepath.Join(outputDir, "debug", "app-debug.apk")
		}
	}

	// Check if artifact exists at expected location
	if _, err := os.Stat(searchPath); err == nil {
		return searchPath, nil
	}

	// If not found in expected location, search for any matching file
	var foundArtifact string
	filepath.Walk(outputDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && filepath.Ext(path) == fileExt {
			foundArtifact = path
			return filepath.SkipAll
		}
		return nil
	})

	if foundArtifact != "" {
		return foundArtifact, nil
	}

	return "", fmt.Errorf("no %s file found in %s", fileExt, outputDir)
}

// copyArtifact copies the artifact (APK or AAB) to the builds directory with a proper name
func (b *Builder) copyArtifact(artifactPath, buildID, buildFormat string) (string, error) {
	// Storage is hardcoded to ./storage
	buildsDir := "./storage/builds"

	// Ensure builds directory exists
	if err := os.MkdirAll(buildsDir, 0755); err != nil {
		return "", err
	}

	// Determine file extension
	fileExt := ".apk"
	if buildFormat == "aab" {
		fileExt = ".aab"
	}

	// Generate final filename
	finalFilename := fmt.Sprintf("%s%s", buildID, fileExt)
	finalPath := filepath.Join(buildsDir, finalFilename)

	// Copy artifact
	source, err := os.Open(artifactPath)
	if err != nil {
		return "", err
	}
	defer source.Close()

	dest, err := os.Create(finalPath)
	if err != nil {
		return "", err
	}
	defer dest.Close()

	if _, err := dest.ReadFrom(source); err != nil {
		return "", err
	}

	b.logger.WithFields(logrus.Fields{
		"source": artifactPath,
		"dest":   finalPath,
		"format": buildFormat,
	}).Debug("Artifact copied successfully")

	return finalPath, nil
}

// findBaseDir locates the base Android project directory
func (b *Builder) 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")
}

// setupKeystoreSigning downloads keystore and configures Gradle signing
func (b *Builder) setupKeystoreSigning(templateDir string, config models.BuildConfig, siteURL, purchaseCode string) error {
	// KeystoreFile contains the keystore ID from Appy
	keystoreID := config.KeystoreFile

	// Download keystore from Appy
	var keystoreData []byte
	var err error
	if siteURL != "" && purchaseCode != "" {
		keystoreData, err = b.appyClient.DownloadKeystoreDynamic(keystoreID, siteURL, purchaseCode)
	} else {
		keystoreData, err = b.appyClient.DownloadKeystore(keystoreID)
	}
	if err != nil {
		return fmt.Errorf("failed to download keystore: %w", err)
	}

	// Find base directory
	baseDir, err := b.findBaseDir(templateDir)
	if err != nil {
		return err
	}

	// Save keystore to template directory
	keystorePath := filepath.Join(baseDir, "app", "release.keystore")
	if err := os.WriteFile(keystorePath, keystoreData, 0600); err != nil {
		return fmt.Errorf("failed to save keystore: %w", err)
	}

	b.logger.WithFields(logrus.Fields{
		"path": keystorePath,
	}).Debug("Keystore saved")

	// Configure Gradle signing (use relative path from app/ directory)
	if err := ConfigureSigning(baseDir, "release.keystore", config); err != nil {
		return fmt.Errorf("failed to configure signing: %w", err)
	}

	b.logger.Debug("Signing configured successfully")
	return nil
}

// cleanup removes temporary build files
func (b *Builder) cleanup(dir string) {
	if err := os.RemoveAll(dir); err != nil {
		b.logger.WithError(err).WithFields(logrus.Fields{
			"path": dir,
		}).Warn("Failed to cleanup directory")
	}
}

// getGradleTask returns the appropriate Gradle task for the given build type and format.
// Supports both APK and AAB formats for both debug and release builds.
func getGradleTask(buildType, buildFormat string) string {
	if buildFormat == "aab" {
		if buildType == "release" {
			return "bundleRelease"
		}
		return "bundleDebug"
	}
	// Default to APK format
	if buildType == "release" {
		return "assembleRelease"
	}
	return "assembleDebug"
}
