package gcs

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"regexp"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iam/v1"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"

	"github.com/orchard9/rdev/internal/domain"
)

// Provisioner implements port.StorageProvisioner using Google Cloud Storage.
type Provisioner struct {
	storageClient *storage.Client
	iamService    *iam.Service
	gcpProjectID  string // GCP project (e.g., "threesix-prod")
	location      string // Bucket location (e.g., "US")
	logger        *slog.Logger
}

// Config holds GCS provisioner configuration.
type Config struct {
	GoogleProjectID string // GCP project ID where buckets will be created
	Location        string // Bucket location (default: "US")
	CredentialsPath string // Path to service account JSON (empty = ADC)
}

// NewProvisioner creates a new GCS bucket provisioner.
func NewProvisioner(cfg Config, logger *slog.Logger) (*Provisioner, error) {
	ctx := context.Background()

	// Apply defaults
	if cfg.Location == "" {
		cfg.Location = "US"
	}

	// Create storage client
	// Note: If CredentialsPath is provided, it should be set in GOOGLE_APPLICATION_CREDENTIALS
	// env var before starting the service. This avoids deprecated WithCredentialsFile/JSON.
	// The SDK will automatically use ADC (Application Default Credentials).
	var opts []option.ClientOption
	if cfg.CredentialsPath != "" {
		// Set environment variable for this process so SDK uses ADC
		if err := os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", cfg.CredentialsPath); err != nil {
			return nil, fmt.Errorf("set GOOGLE_APPLICATION_CREDENTIALS: %w", err)
		}
	}

	storageClient, err := storage.NewClient(ctx, opts...)
	if err != nil {
		return nil, fmt.Errorf("create storage client: %w", err)
	}

	// Create IAM service for service account management
	iamService, err := iam.NewService(ctx, opts...)
	if err != nil {
		return nil, fmt.Errorf("create IAM service: %w", err)
	}

	p := &Provisioner{
		storageClient: storageClient,
		iamService:    iamService,
		gcpProjectID:  cfg.GoogleProjectID,
		location:      cfg.Location,
		logger:        logger,
	}

	// Verify connection
	if err := p.TestConnection(ctx); err != nil {
		return nil, fmt.Errorf("gcs connection test failed: %w", err)
	}

	return p, nil
}

// CreateProjectBucket provisions a GCS bucket with service account and IAM bindings.
func (p *Provisioner) CreateProjectBucket(ctx context.Context, projectID string) (*domain.StorageCredentials, error) {
	bucketName := p.bucketNameFor(projectID)
	saEmail := p.serviceAccountEmailFor(projectID)

	// 1. Check if bucket already exists
	bucket := p.storageClient.Bucket(bucketName)
	if _, err := bucket.Attrs(ctx); err == nil {
		return nil, fmt.Errorf("bucket already exists: %s", bucketName)
	}

	// 2. Create bucket with lifecycle rules
	if err := bucket.Create(ctx, p.gcpProjectID, &storage.BucketAttrs{
		Location:     p.location,
		StorageClass: "STANDARD",
		Lifecycle: storage.Lifecycle{
			Rules: []storage.LifecycleRule{
				// Delete temp files after 24 hours
				{
					Action: storage.LifecycleAction{Type: "Delete"},
					Condition: storage.LifecycleCondition{
						MatchesPrefix: []string{"temp/"},
						AgeInDays:     1,
					},
				},
			},
		},
		CORS: []storage.CORS{
			{
				MaxAge:          3600 * time.Second,
				Methods:         []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
				Origins:         []string{"https://*.threesix.ai"},
				ResponseHeaders: []string{"Content-Type", "ETag"},
			},
		},
	}); err != nil {
		return nil, fmt.Errorf("create bucket: %w", err)
	}

	// 3. Create service account
	accountID := fmt.Sprintf("project-%s-storage", sanitizeForGCP(projectID))
	sa, err := p.iamService.Projects.ServiceAccounts.Create(
		fmt.Sprintf("projects/%s", p.gcpProjectID),
		&iam.CreateServiceAccountRequest{
			AccountId: accountID,
			ServiceAccount: &iam.ServiceAccount{
				DisplayName: fmt.Sprintf("Storage for %s", projectID),
			},
		},
	).Context(ctx).Do()
	if err != nil {
		// Rollback bucket
		_ = bucket.Delete(ctx)
		return nil, fmt.Errorf("create service account: %w", err)
	}

	// 4. Grant bucket permissions to service account
	policy, err := bucket.IAM().Policy(ctx)
	if err != nil {
		return nil, fmt.Errorf("get bucket policy: %w", err)
	}

	policy.Add(saEmail, "roles/storage.objectAdmin")
	if err := bucket.IAM().SetPolicy(ctx, policy); err != nil {
		return nil, fmt.Errorf("set bucket policy: %w", err)
	}

	// 5. Create service account key
	key, err := p.iamService.Projects.ServiceAccounts.Keys.Create(
		sa.Name,
		&iam.CreateServiceAccountKeyRequest{},
	).Context(ctx).Do()
	if err != nil {
		return nil, fmt.Errorf("create service account key: %w", err)
	}

	// 6. Return credentials
	return &domain.StorageCredentials{
		ProjectID:          projectID,
		BucketName:         bucketName,
		ServiceAccountJSON: key.PrivateKeyData, // Base64-encoded JSON
		Location:           p.location,
		PublicURLPrefix:    fmt.Sprintf("https://storage.googleapis.com/%s", bucketName),
		CreatedAt:          time.Now(),
	}, nil
}

// DeleteProjectBucket removes bucket and service account.
func (p *Provisioner) DeleteProjectBucket(ctx context.Context, projectID string, force bool) error {
	bucketName := p.bucketNameFor(projectID)
	bucket := p.storageClient.Bucket(bucketName)

	// Delete all objects if force=true
	if force {
		it := bucket.Objects(ctx, nil)
		for {
			attrs, err := it.Next()
			if err == iterator.Done {
				break
			}
			if err != nil {
				return fmt.Errorf("list objects: %w", err)
			}
			if err := bucket.Object(attrs.Name).Delete(ctx); err != nil {
				p.logger.Warn("failed to delete object", "name", attrs.Name, "error", err)
			}
		}
	}

	// Delete bucket
	if err := bucket.Delete(ctx); err != nil {
		return fmt.Errorf("delete bucket: %w", err)
	}

	// Delete service account
	saEmail := p.serviceAccountEmailFor(projectID)
	saName := fmt.Sprintf("projects/%s/serviceAccounts/%s", p.gcpProjectID, saEmail)
	if _, err := p.iamService.Projects.ServiceAccounts.Delete(saName).Context(ctx).Do(); err != nil {
		p.logger.Warn("failed to delete service account", "email", saEmail, "error", err)
	}

	return nil
}

// GetProjectBucket checks if bucket exists and returns metadata.
func (p *Provisioner) GetProjectBucket(ctx context.Context, projectID string) (*domain.StorageCredentials, error) {
	bucketName := p.bucketNameFor(projectID)
	bucket := p.storageClient.Bucket(bucketName)

	attrs, err := bucket.Attrs(ctx)
	if err != nil {
		if err == storage.ErrBucketNotExist {
			return nil, nil
		}
		return nil, fmt.Errorf("get bucket attrs: %w", err)
	}

	return &domain.StorageCredentials{
		ProjectID:       projectID,
		BucketName:      bucketName,
		Location:        attrs.Location,
		PublicURLPrefix: fmt.Sprintf("https://storage.googleapis.com/%s", bucketName),
		CreatedAt:       attrs.Created,
	}, nil
}

// TestConnection verifies GCS connectivity.
func (p *Provisioner) TestConnection(ctx context.Context) error {
	// Try to list buckets as connection test
	it := p.storageClient.Buckets(ctx, p.gcpProjectID)
	if _, err := it.Next(); err != nil && err != iterator.Done {
		return fmt.Errorf("list buckets failed: %w", err)
	}
	return nil
}

// Close cleans up resources.
func (p *Provisioner) Close() error {
	return p.storageClient.Close()
}

// Helper functions
func (p *Provisioner) bucketNameFor(projectID string) string {
	return fmt.Sprintf("project-%s-media", sanitizeForGCP(projectID))
}

func (p *Provisioner) serviceAccountEmailFor(projectID string) string {
	return fmt.Sprintf("project-%s-storage@%s.iam.gserviceaccount.com",
		sanitizeForGCP(projectID), p.gcpProjectID)
}

func sanitizeForGCP(s string) string {
	// GCP resource names: lowercase alphanumeric and hyphens only
	s = strings.ToLower(s)
	s = regexp.MustCompile(`[^a-z0-9-]+`).ReplaceAllString(s, "-")
	s = strings.Trim(s, "-")
	return s
}
