package cloud

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/ovh/go-ovh/ovh"
)

// OVHProvider implements the Provider interface for OVH Cloud
type OVHProvider struct {
	client     *ovh.Client
	projectID  string
	region     string
	configured bool
}

// NewOVHProvider creates a new OVH provider
func NewOVHProvider() Provider {
	return &OVHProvider{}
}

func init() {
	RegisterProvider("ovh", NewOVHProvider)
}

// Initialize sets up the OVH provider with credentials and configuration
func (p *OVHProvider) Initialize(config map[string]string) error {
	// Check required configuration
	requiredKeys := []string{"application_key", "application_secret", "consumer_key", "project_id"}
	for _, key := range requiredKeys {
		if _, ok := config[key]; !ok {
			return fmt.Errorf("missing required configuration key: %s", key)
		}
	}

	// Create OVH client
	client, err := ovh.NewClient(
		"ovh-eu", // Endpoint (can be configurable)
		config["application_key"],
		config["application_secret"],
		config["consumer_key"],
	)
	if err != nil {
		return fmt.Errorf("failed to create OVH client: %w", err)
	}

	p.client = client
	p.projectID = config["project_id"]

	// Set default region if provided
	if region, ok := config["region"]; ok {
		p.region = region
	}

	p.configured = true
	return nil
}

// Validate checks if the OVH provider credentials are valid
func (p *OVHProvider) Validate(ctx context.Context) (bool, error) {
	if !p.configured {
		return false, errors.New("provider not configured")
	}

	// Try to get project info to verify credentials
	path := fmt.Sprintf("/cloud/project/%s", p.projectID)
	var project map[string]interface{}

	err := p.client.Get(path, &project)
	if err != nil {
		return false, fmt.Errorf("validation failed: %w", err)
	}

	return true, nil
}

// ListRegions lists all available OVH regions
func (p *OVHProvider) ListRegions(ctx context.Context) ([]Region, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/region", p.projectID)
	var regionIDs []string

	err := p.client.Get(path, &regionIDs)
	if err != nil {
		return nil, fmt.Errorf("failed to list regions: %w", err)
	}

	regions := make([]Region, 0, len(regionIDs))
	for _, id := range regionIDs {
		var regionDetails struct {
			Name          string `json:"name"`
			ContinentCode string `json:"continentCode"`
			Status        string `json:"status"`
		}

		regionPath := fmt.Sprintf("/cloud/project/%s/region/%s", p.projectID, id)
		err := p.client.Get(regionPath, &regionDetails)
		if err != nil {
			continue // Skip this region if we can't get details
		}

		regions = append(regions, Region{
			ID:   id,
			Name: regionDetails.Name,
			Zone: regionDetails.ContinentCode,
		})
	}

	return regions, nil
}

// ListInstanceSizes lists available VM sizes (flavors) in OVH
func (p *OVHProvider) ListInstanceSizes(ctx context.Context, region string) ([]InstanceSize, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	if region == "" {
		region = p.region
	}

	if region == "" {
		return nil, errors.New("region must be specified")
	}

	path := fmt.Sprintf("/cloud/project/%s/flavor?region=%s", p.projectID, region)
	var flavors []struct {
		ID          string  `json:"id"`
		Name        string  `json:"name"`
		Vcpus       int     `json:"vcpus"`
		RAM         int     `json:"ram"`  // in MB
		Disk        int     `json:"disk"` // in GB
		Type        string  `json:"type"`
		HourlyPrice float64 `json:"hourlyPrice"`
	}

	err := p.client.Get(path, &flavors)
	if err != nil {
		return nil, fmt.Errorf("failed to list flavors: %w", err)
	}

	sizes := make([]InstanceSize, 0, len(flavors))
	for _, flavor := range flavors {
		sizes = append(sizes, InstanceSize{
			ID:       flavor.ID,
			Name:     flavor.Name,
			CPUCores: flavor.Vcpus,
			MemoryGB: flavor.RAM / 1024, // Convert MB to GB
			DiskGB:   flavor.Disk,
			Price:    flavor.HourlyPrice,
		})
	}

	return sizes, nil
}

// ListInstances lists all instances in OVH
func (p *OVHProvider) ListInstances(ctx context.Context) ([]Instance, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/instance", p.projectID)
	var ovhInstances []struct {
		ID          string    `json:"id"`
		Name        string    `json:"name"`
		Status      string    `json:"status"`
		Created     time.Time `json:"created"`
		Region      string    `json:"region"`
		FlavorID    string    `json:"flavorId"`
		ImageID     string    `json:"imageId"`
		IPAddresses []struct {
			IP      string `json:"ip"`
			Type    string `json:"type"`    // public or private
			Version int    `json:"version"` // 4 or 6
		} `json:"ipAddresses"`
	}

	err := p.client.Get(path, &ovhInstances)
	if err != nil {
		return nil, fmt.Errorf("failed to list instances: %w", err)
	}

	instances := make([]Instance, 0, len(ovhInstances))
	for _, ovhInstance := range ovhInstances {
		instance := Instance{
			ID:        ovhInstance.ID,
			Name:      ovhInstance.Name,
			Region:    ovhInstance.Region,
			Size:      ovhInstance.FlavorID,
			ImageID:   ovhInstance.ImageID,
			Status:    mapOVHStatus(ovhInstance.Status),
			CreatedAt: ovhInstance.Created,
		}

		// Extract IP addresses
		for _, ip := range ovhInstance.IPAddresses {
			if ip.Version == 4 { // Only use IPv4 for now
				if ip.Type == "public" {
					instance.IPAddress = ip.IP
				} else {
					instance.PrivateIP = ip.IP
				}
			}
		}

		instances = append(instances, instance)
	}

	return instances, nil
}

// GetInstance gets a specific instance by ID
func (p *OVHProvider) GetInstance(ctx context.Context, id string) (*Instance, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/instance/%s", p.projectID, id)
	var ovhInstance struct {
		ID          string    `json:"id"`
		Name        string    `json:"name"`
		Status      string    `json:"status"`
		Created     time.Time `json:"created"`
		Region      string    `json:"region"`
		FlavorID    string    `json:"flavorId"`
		ImageID     string    `json:"imageId"`
		IPAddresses []struct {
			IP      string `json:"ip"`
			Type    string `json:"type"`    // public or private
			Version int    `json:"version"` // 4 or 6
		} `json:"ipAddresses"`
	}

	err := p.client.Get(path, &ovhInstance)
	if err != nil {
		return nil, fmt.Errorf("failed to get instance: %w", err)
	}

	instance := &Instance{
		ID:        ovhInstance.ID,
		Name:      ovhInstance.Name,
		Region:    ovhInstance.Region,
		Size:      ovhInstance.FlavorID,
		ImageID:   ovhInstance.ImageID,
		Status:    mapOVHStatus(ovhInstance.Status),
		CreatedAt: ovhInstance.Created,
	}

	// Extract IP addresses
	for _, ip := range ovhInstance.IPAddresses {
		if ip.Version == 4 { // Only use IPv4 for now
			if ip.Type == "public" {
				instance.IPAddress = ip.IP
			} else {
				instance.PrivateIP = ip.IP
			}
		}
	}

	return instance, nil
}

// CreateInstance creates a new instance in OVH
func (p *OVHProvider) CreateInstance(ctx context.Context, opts InstanceCreateOpts) (*Instance, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	// Prepare create request
	path := fmt.Sprintf("/cloud/project/%s/instance", p.projectID)
	request := struct {
		Name     string   `json:"name"`
		Region   string   `json:"region"`
		FlavorID string   `json:"flavorId"`
		ImageID  string   `json:"imageId"`
		SSHKeyID []string `json:"sshKeyId,omitempty"`
		UserData string   `json:"userData,omitempty"`
		Networks []string `json:"networks,omitempty"`
	}{
		Name:     opts.Name,
		Region:   opts.Region,
		FlavorID: opts.Size,
		ImageID:  opts.ImageID,
		SSHKeyID: opts.SSHKeyIDs,
		UserData: opts.UserData,
	}

	var result struct {
		ID      string `json:"id"`
		Status  string `json:"status"`
		Created string `json:"created"`
	}

	err := p.client.Post(path, request, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to create instance: %w", err)
	}

	// Fetch the full instance details
	createdInstance, err := p.GetInstance(ctx, result.ID)
	if err != nil {
		return nil, fmt.Errorf("instance created but failed to retrieve details: %w", err)
	}

	return createdInstance, nil
}

// DeleteInstance deletes an instance in OVH
func (p *OVHProvider) DeleteInstance(ctx context.Context, id string) error {
	if !p.configured {
		return errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/instance/%s", p.projectID, id)
	err := p.client.Delete(path, nil)
	if err != nil {
		return fmt.Errorf("failed to delete instance: %w", err)
	}

	return nil
}

// StartInstance starts an instance in OVH
func (p *OVHProvider) StartInstance(ctx context.Context, id string) error {
	if !p.configured {
		return errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/instance/%s/start", p.projectID, id)
	err := p.client.Post(path, nil, nil)
	if err != nil {
		return fmt.Errorf("failed to start instance: %w", err)
	}

	return nil
}

// StopInstance stops an instance in OVH
func (p *OVHProvider) StopInstance(ctx context.Context, id string) error {
	if !p.configured {
		return errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/instance/%s/stop", p.projectID, id)
	err := p.client.Post(path, nil, nil)
	if err != nil {
		return fmt.Errorf("failed to stop instance: %w", err)
	}

	return nil
}

// RestartInstance restarts an instance in OVH
func (p *OVHProvider) RestartInstance(ctx context.Context, id string) error {
	if !p.configured {
		return errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/instance/%s/reboot", p.projectID, id)
	err := p.client.Post(path, nil, nil)
	if err != nil {
		return fmt.Errorf("failed to restart instance: %w", err)
	}

	return nil
}

// ListImages lists available OS images in OVH
func (p *OVHProvider) ListImages(ctx context.Context) ([]Image, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	// Get all images
	path := fmt.Sprintf("/cloud/project/%s/image", p.projectID)
	var ovhImages []struct {
		ID           string    `json:"id"`
		Name         string    `json:"name"`
		Region       string    `json:"region"`
		Visibility   string    `json:"visibility"`
		Type         string    `json:"type"`
		Status       string    `json:"status"`
		CreationDate time.Time `json:"creationDate"`
		MinDisk      int       `json:"minDisk"`
		Size         int       `json:"size"`
	}

	err := p.client.Get(path, &ovhImages)
	if err != nil {
		return nil, fmt.Errorf("failed to list images: %w", err)
	}

	images := make([]Image, 0, len(ovhImages))
	for _, ovhImage := range ovhImages {
		images = append(images, Image{
			ID:          ovhImage.ID,
			Name:        ovhImage.Name,
			Description: ovhImage.Type,
			Type:        ovhImage.Type,
			Status:      ovhImage.Status,
			CreatedAt:   ovhImage.CreationDate,
			MinDiskGB:   ovhImage.MinDisk,
			SizeGB:      ovhImage.Size / (1024 * 1024 * 1024), // Convert bytes to GB
		})
	}

	return images, nil
}

// ListSSHKeys lists SSH keys in OVH
func (p *OVHProvider) ListSSHKeys(ctx context.Context) ([]SSHKey, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/sshkey", p.projectID)
	var ovhKeys []struct {
		ID          string    `json:"id"`
		Name        string    `json:"name"`
		PublicKey   string    `json:"publicKey"`
		Fingerprint string    `json:"fingerprint"`
		CreatedAt   time.Time `json:"creationDate"`
	}

	err := p.client.Get(path, &ovhKeys)
	if err != nil {
		return nil, fmt.Errorf("failed to list SSH keys: %w", err)
	}

	keys := make([]SSHKey, 0, len(ovhKeys))
	for _, ovhKey := range ovhKeys {
		keys = append(keys, SSHKey{
			ID:          ovhKey.ID,
			Name:        ovhKey.Name,
			PublicKey:   ovhKey.PublicKey,
			Fingerprint: ovhKey.Fingerprint,
			CreatedAt:   ovhKey.CreatedAt,
		})
	}

	return keys, nil
}

// CreateSSHKey creates a new SSH key in OVH
func (p *OVHProvider) CreateSSHKey(ctx context.Context, name, publicKey string) (*SSHKey, error) {
	if !p.configured {
		return nil, errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/sshkey", p.projectID)
	request := struct {
		Name      string `json:"name"`
		PublicKey string `json:"publicKey"`
		Region    string `json:"region,omitempty"`
	}{
		Name:      name,
		PublicKey: publicKey,
		Region:    p.region, // Optional region
	}

	var result struct {
		ID          string    `json:"id"`
		Name        string    `json:"name"`
		PublicKey   string    `json:"publicKey"`
		Fingerprint string    `json:"fingerprint"`
		CreatedAt   time.Time `json:"creationDate"`
	}

	err := p.client.Post(path, request, &result)
	if err != nil {
		return nil, fmt.Errorf("failed to create SSH key: %w", err)
	}

	return &SSHKey{
		ID:          result.ID,
		Name:        result.Name,
		PublicKey:   result.PublicKey,
		Fingerprint: result.Fingerprint,
		CreatedAt:   result.CreatedAt,
	}, nil
}

// DeleteSSHKey deletes an SSH key in OVH
func (p *OVHProvider) DeleteSSHKey(ctx context.Context, id string) error {
	if !p.configured {
		return errors.New("provider not configured")
	}

	path := fmt.Sprintf("/cloud/project/%s/sshkey/%s", p.projectID, id)
	err := p.client.Delete(path, nil)
	if err != nil {
		return fmt.Errorf("failed to delete SSH key: %w", err)
	}

	return nil
}

// GetInstanceStatus gets the current status of an instance in OVH
func (p *OVHProvider) GetInstanceStatus(ctx context.Context, id string) (string, error) {
	instance, err := p.GetInstance(ctx, id)
	if err != nil {
		return "", err
	}

	return instance.Status, nil
}

// WaitForInstanceStatus waits for an instance to reach a specific status
func (p *OVHProvider) WaitForInstanceStatus(ctx context.Context, id, status string, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)

	for time.Now().Before(deadline) {
		currentStatus, err := p.GetInstanceStatus(ctx, id)
		if err != nil {
			return err
		}

		if currentStatus == status {
			return nil
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-time.After(5 * time.Second):
			// Wait 5 seconds before next check
		}
	}

	return fmt.Errorf("timeout waiting for instance %s to reach status %s", id, status)
}

// mapOVHStatus maps OVH instance status to standardized status
func mapOVHStatus(ovhStatus string) string {
	switch ovhStatus {
	case "ACTIVE":
		return "Running"
	case "BUILD":
		return "Creating"
	case "BUILDING":
		return "Creating"
	case "SHUTOFF":
		return "Stopped"
	case "DELETED":
		return "Terminated"
	case "SOFT_DELETED":
		return "Terminated"
	case "HARD_REBOOT":
		return "Restarting"
	case "REBOOT":
		return "Restarting"
	case "RESCUE":
		return "Running"
	case "ERROR":
		return "Error"
	case "PAUSED":
		return "Stopped"
	case "SUSPENDED":
		return "Stopped"
	case "STOPPING":
		return "Stopping"
	default:
		return ovhStatus
	}
}
