package aws

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"

	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/apigateway"
	"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
)

func (c *AWSCollector) collectAPIGateway(ctx context.Context, region string, result *collector.AssetResult) error {
	var errs []error
	// Collect REST APIs (API Gateway v1)
	if err := c.collectRESTAPIs(ctx, region, result); err != nil {
		errs = append(errs, fmt.Errorf("rest apis: %w", err))
	}

	// Collect HTTP/WebSocket APIs (API Gateway v2)
	if err := c.collectHTTPAPIs(ctx, region, result); err != nil {
		errs = append(errs, fmt.Errorf("http apis: %w", err))
	}

	return errors.Join(errs...)
}

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

	paginator := apigateway.NewGetRestApisPaginator(svc, &apigateway.GetRestApisInput{})

	for paginator.HasMorePages() {
		page, err := paginator.NextPage(ctx)
		if err != nil {
			return err
		}

		for _, api := range page.Items {
			if api.Id == nil {
				continue
			}

			apiID := *api.Id
			name := ""
			if api.Name != nil {
				name = *api.Name
			} else {
				name = apiID
			}

			// Create API Gateway asset
			asset := c.newBaseAsset(apiID, name, string(model.AssetTypeApplication), "active", region)
			c.setService(&asset, "api_gateway")

			// Collect stages
			stages, err := c.collectAPIStages(ctx, svc, apiID)
			if err != nil {
				return fmt.Errorf("collect REST API stages for %s: %w", apiID, err)
			}

			privateEndpoint := isPrivateRESTAPIEndpoint(api.EndpointConfiguration)
			meta := map[string]interface{}{
				"api_id":          apiID,
				"api_type":        "REST",
				"description":     api.Description,
				"endpoint_config": api.EndpointConfiguration,
				"private":         privateEndpoint,
				"stages":          stages,
				"created_date":    api.CreatedDate,
			}
			asset.Metadata = coremodel.JSONB(meta)
			result.Assets = append(result.Assets, asset)

			// Create domain assets for each stage
			for _, stage := range stages {
				// REST API endpoint format: https://{api-id}.execute-api.{region}.amazonaws.com/{stage}
				endpoint := fmt.Sprintf("%s.execute-api.%s.amazonaws.com", apiID, region)
				_ = stage

				domainAsset := model.DomainAsset{
					Asset:       model.Asset{NativeID: asset.NativeID},
					DomainName:  endpoint,
					DomainClass: model.DomainClassServiceHostname,
					Registrar:   "apigateway",
				}
				setServiceEndpointDomainDefaults(&domainAsset, "api_gateway", !privateEndpoint)
				c.setService(&domainAsset.Asset, "api_gateway")
				result.DomainAssets = append(result.DomainAssets, domainAsset)

				// Add HTTPS port (API Gateway only supports HTTPS)
				result.Ports = append(result.Ports, model.IPPortIndex{
					Asset:             model.Asset{NativeID: asset.NativeID},
					IPAddress:         endpoint,
					Port:              443,
					Protocol:          "tcp",
					ObservationSource: model.PortObservationSourceInferredWeb,
					ServiceName:       "https",
					Exposure:          endpointExposure(!privateEndpoint),
					Status:            "open",
				})
			}

			if err := c.collectRESTAPICustomDomains(ctx, svc, apiID, region, result); err != nil {
				return fmt.Errorf("collect REST API custom domains for %s: %w", apiID, err)
			}
		}
	}

	return nil
}

func (c *AWSCollector) collectAPIStages(ctx context.Context, svc *apigateway.Client, apiID string) ([]string, error) {
	resp, err := svc.GetStages(ctx, &apigateway.GetStagesInput{
		RestApiId: &apiID,
	})
	if err != nil {
		return nil, err
	}

	stages := make([]string, 0, len(resp.Item))
	for _, stage := range resp.Item {
		if stage.StageName != nil {
			stages = append(stages, *stage.StageName)
		}
	}

	return stages, nil
}

func (c *AWSCollector) collectRESTAPICustomDomains(ctx context.Context, svc *apigateway.Client, apiID, region string, result *collector.AssetResult) error {
	// Get custom domain names
	paginator := apigateway.NewGetDomainNamesPaginator(svc, &apigateway.GetDomainNamesInput{})

	for paginator.HasMorePages() {
		page, err := paginator.NextPage(ctx)
		if err != nil {
			return err
		}

		for _, domain := range page.Items {
			if domain.DomainName == nil {
				continue
			}

			domainName := normalizeAPIGatewayHostname(*domain.DomainName)
			if domainName == "" {
				continue
			}
			mapped, err := c.restCustomDomainBelongsToAPI(ctx, svc, domainName, apiID)
			if err != nil {
				return fmt.Errorf("inspect REST API custom-domain mappings for %s: %w", domainName, err)
			}
			if !mapped {
				continue
			}

			domainAsset := model.DomainAsset{
				Asset:       model.Asset{NativeID: apiID},
				DomainName:  domainName,
				DomainClass: model.DomainClassMappedCustom,
				Registrar:   "custom",
			}
			setServiceEndpointDomainDefaults(&domainAsset, "api_gateway", true)
			c.setService(&domainAsset.Asset, "api_gateway")

			result.DomainAssets = append(result.DomainAssets, domainAsset)

			// Add HTTPS port
			result.Ports = append(result.Ports, model.IPPortIndex{
				Asset:             model.Asset{NativeID: apiID},
				IPAddress:         domainName,
				Port:              443,
				Protocol:          "tcp",
				ObservationSource: model.PortObservationSourceInferredWeb,
				ServiceName:       "https",
				Exposure:          "public",
				Status:            "open",
			})
		}
	}

	return nil
}

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

	var nextToken *string
	for {
		resp, err := svc.GetApis(ctx, &apigatewayv2.GetApisInput{
			NextToken: nextToken,
		})
		if err != nil {
			return err
		}
		for _, api := range resp.Items {
			if api.ApiId == nil {
				continue
			}

			apiID := *api.ApiId
			name := ""
			if api.Name != nil {
				name = *api.Name
			} else {
				name = apiID
			}

			protocolType := "HTTP"
			if api.ProtocolType != "" {
				protocolType = string(api.ProtocolType)
			}

			// Create API Gateway V2 asset
			asset := c.newBaseAsset(apiID, name, string(model.AssetTypeApplication), "active", region)
			c.setService(&asset, "api_gateway")

			// Collect stages
			stages, err := c.collectHTTPAPIStages(ctx, svc, apiID)
			if err != nil {
				return fmt.Errorf("collect HTTP API stages for %s: %w", apiID, err)
			}

			meta := map[string]interface{}{
				"api_id":       apiID,
				"api_type":     protocolType, // HTTP or WEBSOCKET
				"api_endpoint": api.ApiEndpoint,
				"description":  api.Description,
				"stages":       stages,
				"created_date": api.CreatedDate,
			}
			asset.Metadata = coremodel.JSONB(meta)
			result.Assets = append(result.Assets, asset)

			// Add API endpoint as domain asset
			if api.ApiEndpoint != nil {
				endpoint := normalizeAPIGatewayHostname(*api.ApiEndpoint)
				if endpoint == "" {
					continue
				}
				now := time.Now()

				domainAsset := model.DomainAsset{
					Asset:        model.Asset{NativeID: asset.NativeID},
					DomainName:   endpoint,
					DomainClass:  model.DomainClassServiceHostname,
					Registrar:    "apigateway",
					ZoneType:     "public",
					Status:       "active",
					LastSyncedAt: &now,
					FirstSeenAt:  &now,
					LastSeenAt:   &now,
				}
				c.setService(&domainAsset.Asset, "api_gateway")
				result.DomainAssets = append(result.DomainAssets, domainAsset)

				// Add HTTPS port
				result.Ports = append(result.Ports, model.IPPortIndex{
					Asset:             model.Asset{NativeID: asset.NativeID},
					IPAddress:         endpoint,
					Port:              443,
					Protocol:          "tcp",
					ObservationSource: model.PortObservationSourceInferredWeb,
					ServiceName:       "https",
					Exposure:          "public",
					Status:            "open",
				})
			}
			if err := c.collectHTTPAPICustomDomains(ctx, svc, apiID, result); err != nil {
				return fmt.Errorf("collect HTTP API custom domains for %s: %w", apiID, err)
			}
		}
		if resp.NextToken == nil || *resp.NextToken == "" {
			break
		}
		nextToken = resp.NextToken
	}

	return nil
}

func (c *AWSCollector) collectHTTPAPIStages(ctx context.Context, svc *apigatewayv2.Client, apiID string) ([]string, error) {
	var stages []string
	var nextToken *string
	for {
		resp, err := svc.GetStages(ctx, &apigatewayv2.GetStagesInput{
			ApiId:     &apiID,
			NextToken: nextToken,
		})
		if err != nil {
			return nil, err
		}
		for _, stage := range resp.Items {
			if stage.StageName != nil {
				stages = append(stages, *stage.StageName)
			}
		}
		if resp.NextToken == nil || *resp.NextToken == "" {
			break
		}
		nextToken = resp.NextToken
	}

	return stages, nil
}

func (c *AWSCollector) collectHTTPAPICustomDomains(ctx context.Context, svc *apigatewayv2.Client, apiID string, result *collector.AssetResult) error {
	var nextToken *string
	for {
		resp, err := svc.GetDomainNames(ctx, &apigatewayv2.GetDomainNamesInput{
			NextToken: nextToken,
		})
		if err != nil {
			return err
		}
		for _, domain := range resp.Items {
			if domain.DomainName == nil {
				continue
			}
			domainName := normalizeAPIGatewayHostname(*domain.DomainName)
			if domainName == "" {
				continue
			}
			mapped, err := c.httpCustomDomainBelongsToAPI(ctx, svc, domainName, apiID)
			if err != nil {
				return fmt.Errorf("inspect HTTP API custom-domain mappings for %s: %w", domainName, err)
			}
			if !mapped {
				continue
			}
			domainAsset := model.DomainAsset{
				Asset:       model.Asset{NativeID: apiID},
				DomainName:  domainName,
				DomainClass: model.DomainClassMappedCustom,
				Registrar:   "custom",
			}
			setServiceEndpointDomainDefaults(&domainAsset, "api_gateway", true)
			c.setService(&domainAsset.Asset, "api_gateway")
			result.DomainAssets = append(result.DomainAssets, domainAsset)
			result.Ports = append(result.Ports, model.IPPortIndex{
				Asset:             model.Asset{NativeID: apiID},
				IPAddress:         domainName,
				Port:              443,
				Protocol:          "tcp",
				ObservationSource: model.PortObservationSourceInferredWeb,
				ServiceName:       "https",
				Exposure:          "public",
				Status:            "open",
			})
		}
		if resp.NextToken == nil || *resp.NextToken == "" {
			break
		}
		nextToken = resp.NextToken
	}
	return nil
}

func (c *AWSCollector) httpCustomDomainBelongsToAPI(ctx context.Context, svc *apigatewayv2.Client, domainName, apiID string) (bool, error) {
	var nextToken *string
	for {
		resp, err := svc.GetApiMappings(ctx, &apigatewayv2.GetApiMappingsInput{
			DomainName: &domainName,
			NextToken:  nextToken,
		})
		if err != nil {
			return false, err
		}
		for _, mapping := range resp.Items {
			if mapping.ApiId != nil && *mapping.ApiId == apiID {
				return true, nil
			}
		}
		if resp.NextToken == nil || *resp.NextToken == "" {
			break
		}
		nextToken = resp.NextToken
	}
	return false, nil
}

func (c *AWSCollector) restCustomDomainBelongsToAPI(ctx context.Context, svc *apigateway.Client, domainName, apiID string) (bool, error) {
	paginator := apigateway.NewGetBasePathMappingsPaginator(svc, &apigateway.GetBasePathMappingsInput{
		DomainName: &domainName,
	})
	for paginator.HasMorePages() {
		resp, err := paginator.NextPage(ctx)
		if err != nil {
			return false, err
		}
		for _, mapping := range resp.Items {
			if mapping.RestApiId != nil && *mapping.RestApiId == apiID {
				return true, nil
			}
		}
	}

	return false, nil
}

func normalizeAPIGatewayHostname(raw string) string {
	value := strings.TrimSpace(raw)
	if value == "" {
		return ""
	}

	if parsed, err := url.Parse(value); err == nil && parsed.Host != "" {
		return strings.ToLower(parsed.Hostname())
	}

	value = strings.TrimPrefix(value, "https://")
	value = strings.TrimPrefix(value, "http://")
	value = strings.Trim(value, "/")
	return strings.ToLower(value)
}
