package aws

import (
	"context"
	"errors"
	"fmt"

	coremodel "security-center-backend/internal/model"
	"security-center-backend/internal/modules/caasm/collector"
	model "security-center-backend/internal/modules/caasm/model"

	"github.com/aws/aws-sdk-go-v2/service/elasticache"
)

func (c *AWSCollector) collectElastiCache(ctx context.Context, region string, result *collector.AssetResult) error {
	svc := elasticache.NewFromConfig(c.cfg, func(o *elasticache.Options) {
		o.Region = region
	})

	var errs []error
	if err := c.collectElastiCacheRedis(ctx, svc, region, result); err != nil {
		errs = append(errs, fmt.Errorf("redis: %w", err))
	}

	if err := c.collectElastiCacheMemcached(ctx, svc, region, result); err != nil {
		errs = append(errs, fmt.Errorf("memcached: %w", err))
	}

	return errors.Join(errs...)
}

func (c *AWSCollector) collectElastiCacheRedis(ctx context.Context, svc *elasticache.Client, region string, result *collector.AssetResult) error {
	paginator := elasticache.NewDescribeReplicationGroupsPaginator(svc, &elasticache.DescribeReplicationGroupsInput{})
	var errs []error
	for paginator.HasMorePages() {
		resp, err := paginator.NextPage(ctx)
		if err != nil {
			return err
		}
		for _, rg := range resp.ReplicationGroups {
			if rg.ReplicationGroupId == nil {
				continue
			}

			rgID := *rg.ReplicationGroupId
			rgArn := ""
			if rg.ARN != nil {
				rgArn = *rg.ARN
			}

			// Extract endpoints
			primaryEndpoint := ""
			primaryPort := uint32(6379) // Default Redis port
			readerEndpoint := ""

			if len(rg.NodeGroups) > 0 {
				ng := rg.NodeGroups[0]
				if ng.PrimaryEndpoint != nil {
					if ng.PrimaryEndpoint.Address != nil {
						primaryEndpoint = *ng.PrimaryEndpoint.Address
					}
					if ng.PrimaryEndpoint.Port != nil {
						primaryPort = uint32(*ng.PrimaryEndpoint.Port)
					}
				}
				if ng.ReaderEndpoint != nil && ng.ReaderEndpoint.Address != nil {
					readerEndpoint = *ng.ReaderEndpoint.Address
				}
			}

			// Extract security group IDs
			securityGroupIds := make([]string, 0)
			if len(rg.MemberClusters) > 0 {
				// Get first cluster to extract security groups
				clusterResp, err := svc.DescribeCacheClusters(ctx, &elasticache.DescribeCacheClustersInput{
					CacheClusterId: &rg.MemberClusters[0],
				})
				if err != nil {
					errs = append(errs, fmt.Errorf("describe cache cluster %s: %w", rg.MemberClusters[0], err))
				} else if len(clusterResp.CacheClusters) > 0 {
					cluster := clusterResp.CacheClusters[0]
					if cluster.SecurityGroups != nil {
						for _, sg := range cluster.SecurityGroups {
							if sg.SecurityGroupId != nil {
								securityGroupIds = append(securityGroupIds, *sg.SecurityGroupId)
							}
						}
					}
				}
			}

			// Create Redis replication group asset
			asset := c.newBaseAsset(rgArn, rgID, string(model.AssetTypeDatabase), "active", region)
			c.setService(&asset, "elasticache")

			meta := map[string]interface{}{
				"replication_group_id":     rgID,
				"engine":                   "redis",
				"engine_version":           rg.CacheNodeType,
				"status":                   rg.Status,
				"num_cache_clusters":       len(rg.MemberClusters),
				"automatic_failover":       rg.AutomaticFailover,
				"multi_az":                 rg.MultiAZ,
				"snapshot_retention_limit": rg.SnapshotRetentionLimit,
				"snapshot_window":          rg.SnapshotWindow,
				"primary_endpoint":         primaryEndpoint,
				"reader_endpoint":          readerEndpoint,
				"port":                     primaryPort,
				"security_groups":          securityGroupIds,
				"transit_encryption":       rg.TransitEncryptionEnabled,
				"at_rest_encryption":       rg.AtRestEncryptionEnabled,
			}
			asset.Metadata = coremodel.JSONB(meta)
			result.Assets = append(result.Assets, asset)

			// Add primary endpoint as domain asset
			if primaryEndpoint != "" {
				domainAsset := model.DomainAsset{
					Asset:       model.Asset{NativeID: asset.NativeID},
					DomainName:  primaryEndpoint,
					DomainClass: model.DomainClassServiceHostname,
					Registrar:   "elasticache",
				}
				setServiceEndpointDomainDefaults(&domainAsset, "elasticache", false)
				c.setService(&domainAsset.Asset, "elasticache")
				result.DomainAssets = append(result.DomainAssets, domainAsset)

				// ElastiCache should NEVER be public - mark as private
				// If somehow public, it's a CRITICAL security issue
				result.Ports = append(result.Ports, model.IPPortIndex{
					Asset:             model.Asset{NativeID: asset.NativeID},
					IPAddress:         primaryEndpoint,
					Port:              primaryPort,
					Protocol:          "tcp",
					ObservationSource: model.PortObservationSourceServiceEndpoint,
					ServiceName:       "redis",
					Exposure:          "private", // ElastiCache is always in VPC, never public
					Status:            "open",
				})
			}

			// Add reader endpoint if available
			if readerEndpoint != "" && readerEndpoint != primaryEndpoint {
				domainAsset := model.DomainAsset{
					Asset:       model.Asset{NativeID: asset.NativeID},
					DomainName:  readerEndpoint,
					DomainClass: model.DomainClassServiceHostname,
					Registrar:   "elasticache",
				}
				setServiceEndpointDomainDefaults(&domainAsset, "elasticache", false)
				c.setService(&domainAsset.Asset, "elasticache")
				result.DomainAssets = append(result.DomainAssets, domainAsset)

				result.Ports = append(result.Ports, model.IPPortIndex{
					Asset:             model.Asset{NativeID: asset.NativeID},
					IPAddress:         readerEndpoint,
					Port:              primaryPort,
					Protocol:          "tcp",
					ObservationSource: model.PortObservationSourceServiceEndpoint,
					ServiceName:       "redis-read",
					Exposure:          "private",
					Status:            "open",
				})
			}
		}
	}

	return errors.Join(errs...)
}

func (c *AWSCollector) collectElastiCacheMemcached(ctx context.Context, svc *elasticache.Client, region string, result *collector.AssetResult) error {
	paginator := elasticache.NewDescribeCacheClustersPaginator(svc, &elasticache.DescribeCacheClustersInput{})
	for paginator.HasMorePages() {
		resp, err := paginator.NextPage(ctx)
		if err != nil {
			return err
		}
		for _, cluster := range resp.CacheClusters {
			// Skip Redis clusters (they're handled by replication groups)
			if cluster.Engine != nil && *cluster.Engine == "redis" {
				continue
			}

			if cluster.CacheClusterId == nil {
				continue
			}

			clusterID := *cluster.CacheClusterId
			clusterArn := ""
			if cluster.ARN != nil {
				clusterArn = *cluster.ARN
			}

			// Extract endpoint
			endpoint := ""
			port := uint32(11211) // Default Memcached port

			if cluster.ConfigurationEndpoint != nil {
				if cluster.ConfigurationEndpoint.Address != nil {
					endpoint = *cluster.ConfigurationEndpoint.Address
				}
				if cluster.ConfigurationEndpoint.Port != nil {
					port = uint32(*cluster.ConfigurationEndpoint.Port)
				}
			} else if len(cluster.CacheNodes) > 0 {
				// If no configuration endpoint, use first node
				node := cluster.CacheNodes[0]
				if node.Endpoint != nil {
					if node.Endpoint.Address != nil {
						endpoint = *node.Endpoint.Address
					}
					if node.Endpoint.Port != nil {
						port = uint32(*node.Endpoint.Port)
					}
				}
			}

			// Extract security group IDs
			securityGroupIds := make([]string, 0)
			if cluster.SecurityGroups != nil {
				for _, sg := range cluster.SecurityGroups {
					if sg.SecurityGroupId != nil {
						securityGroupIds = append(securityGroupIds, *sg.SecurityGroupId)
					}
				}
			}

			// Create Memcached cluster asset
			asset := c.newBaseAsset(clusterArn, clusterID, string(model.AssetTypeDatabase), "active", region)
			c.setService(&asset, "elasticache")

			meta := map[string]interface{}{
				"cluster_id":      clusterID,
				"engine":          cluster.Engine,
				"engine_version":  cluster.EngineVersion,
				"cache_node_type": cluster.CacheNodeType,
				"num_cache_nodes": cluster.NumCacheNodes,
				"status":          cluster.CacheClusterStatus,
				"preferred_az":    cluster.PreferredAvailabilityZone,
				"endpoint":        endpoint,
				"port":            port,
				"security_groups": securityGroupIds,
			}
			asset.Metadata = coremodel.JSONB(meta)
			result.Assets = append(result.Assets, asset)

			// Add endpoint as domain asset
			if endpoint != "" {
				domainAsset := model.DomainAsset{
					Asset:       model.Asset{NativeID: asset.NativeID},
					DomainName:  endpoint,
					DomainClass: model.DomainClassServiceHostname,
					Registrar:   "elasticache",
				}
				setServiceEndpointDomainDefaults(&domainAsset, "elasticache", false)
				c.setService(&domainAsset.Asset, "elasticache")
				result.DomainAssets = append(result.DomainAssets, domainAsset)

				// Memcached should also never be public
				result.Ports = append(result.Ports, model.IPPortIndex{
					Asset:             model.Asset{NativeID: asset.NativeID},
					IPAddress:         endpoint,
					Port:              port,
					Protocol:          "tcp",
					ObservationSource: model.PortObservationSourceServiceEndpoint,
					ServiceName:       "memcached",
					Exposure:          "private",
					Status:            "open",
				})
			}
		}
	}

	return nil
}
