package handler

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"

	coremodel "security-center-backend/internal/model"
	caasmmocks "security-center-backend/internal/modules/caasm/mocks"
	caasmmodel "security-center-backend/internal/modules/caasm/model"
	"security-center-backend/internal/modules/caasm/repository"
	"security-center-backend/internal/modules/caasm/service"
)

func TestToCaasmIPAssetListItemDTOIncludesExposureAndAssetContext(t *testing.T) {
	accountID := uint64(42)
	lastSeen := time.Date(2026, time.May, 18, 10, 30, 0, 0, time.UTC)
	entry := caasmmodel.IPAsset{
		Base: coremodel.Base{
			ID: 9001,
		},
		AssetID:    101,
		IPAddress:  "54.240.1.10",
		Hostname:   "web.example.com",
		IsPublic:   true,
		LastSeenAt: &lastSeen,
		Asset: caasmmodel.Asset{
			Base:           coremodel.Base{ID: 101},
			CloudAccountID: &accountID,
			Name:           "web-server",
			AssetType:      caasmmodel.AssetTypeCompute,
			Service:        "EC2",
			Status:         caasmmodel.AssetStatusActive,
			Environment:    "prod",
			Region:         "us-east-1",
			Provider:       "aws",
			CloudAccount: caasmmodel.CloudAccount{
				Base:     coremodel.Base{ID: accountID},
				Name:     "AWS prod",
				Provider: caasmmodel.CloudProviderAWS,
				Status:   caasmmodel.CloudAccountStatusActive,
			},
		},
	}

	dto := toCaasmIPAssetListItemDTO(entry)

	assert.Equal(t, uint64(9001), dto.ID)
	assert.Equal(t, uint64(101), dto.AssetID)
	assert.Equal(t, "54.240.1.10", dto.IPAddress)
	assert.Equal(t, "web.example.com", dto.Hostname)
	assert.Equal(t, "public", dto.Exposure)
	assert.True(t, dto.IsPublic)
	assert.Equal(t, "web-server", dto.Asset.Name)
	assert.Equal(t, caasmmodel.AssetTypeCompute, dto.Asset.AssetType)
	assert.Equal(t, "EC2", dto.Asset.Service)
	assert.Equal(t, "prod", dto.Asset.Environment)
	assert.Equal(t, "us-east-1", dto.Asset.Region)
	assert.Equal(t, "aws", dto.Asset.Provider)
	require.NotNil(t, dto.Asset.CloudAccount)
	assert.Equal(t, "AWS prod", dto.Asset.CloudAccount.Name)
	assert.Equal(t, caasmmodel.CloudProviderAWS, dto.Asset.CloudAccount.Provider)
	assert.Equal(t, &lastSeen, dto.LastSeenAt)
}

func TestListIPAssetsNormalizesPaginationAndFilters(t *testing.T) {
	gin.SetMode(gin.TestMode)

	repo := &caasmmocks.MockAssetRepository{}
	publicOnly := true
	repo.On("ListIPAssets", mock.Anything, repository.IPAssetFilter{
		Query:          "web",
		IsPublic:       &publicOnly,
		Provider:       caasmmodel.CloudProviderAWS,
		Service:        "EC2",
		CloudAccountID: 42,
		Region:         "us-east-1",
	}, 1, 20).Return([]caasmmodel.IPAsset{
		{
			Base:      coremodel.Base{ID: 9001},
			AssetID:   101,
			IPAddress: "54.240.1.10",
			IsPublic:  true,
			Asset: caasmmodel.Asset{
				Base:      coremodel.Base{ID: 101},
				Name:      "web-server",
				AssetType: caasmmodel.AssetTypeCompute,
				Service:   "EC2",
				Status:    caasmmodel.AssetStatusActive,
			},
		},
	}, int64(1), nil).Once()

	h := &Handler{assets: service.NewAssetService(repo, nil)}
	router := gin.New()
	router.GET("/caasm/ip-assets", h.ListIPAssets)

	req := httptest.NewRequest(http.MethodGet, "/caasm/ip-assets?page=0&page_size=0&q=web&exposure=public&provider=aws&asset_service=EC2&cloud_account_id=42&region=us-east-1", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	require.Equal(t, http.StatusOK, w.Code)
	var apiResp struct {
		Code int `json:"code"`
		Data struct {
			Items []caasmIPAssetListItemDTO `json:"items"`
			Total int64                     `json:"total"`
			Page  int                       `json:"page"`
		} `json:"data"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &apiResp))
	assert.Equal(t, 0, apiResp.Code)
	assert.Equal(t, int64(1), apiResp.Data.Total)
	assert.Equal(t, 1, apiResp.Data.Page)
	require.Len(t, apiResp.Data.Items, 1)
	assert.Equal(t, "public", apiResp.Data.Items[0].Exposure)
	assert.Equal(t, "54.240.1.10", apiResp.Data.Items[0].IPAddress)
	assert.Equal(t, "web-server", apiResp.Data.Items[0].Asset.Name)
	repo.AssertExpectations(t)
}

func TestListAssetsNormalizesNonPositivePagination(t *testing.T) {
	gin.SetMode(gin.TestMode)

	repo := &caasmmocks.MockAssetRepository{}
	repo.On("List", mock.Anything, repository.AssetFilter{}, 1, 20).Return([]caasmmodel.Asset{
		{
			Base:      coremodel.Base{ID: 101},
			Name:      "web-server",
			AssetType: caasmmodel.AssetTypeCompute,
			Service:   "EC2",
			Status:    caasmmodel.AssetStatusActive,
		},
	}, int64(1), nil).Once()

	h := &Handler{assets: service.NewAssetService(repo, nil)}
	router := gin.New()
	router.GET("/caasm/assets", h.ListAssets)

	req := httptest.NewRequest(http.MethodGet, "/caasm/assets?page=0&page_size=0", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	require.Equal(t, http.StatusOK, w.Code)
	var apiResp struct {
		Code int `json:"code"`
		Data struct {
			Items    []caasmAssetListItemDTO `json:"items"`
			Total    int64                   `json:"total"`
			Page     int                     `json:"page"`
			PageSize int                     `json:"page_size"`
		} `json:"data"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &apiResp))
	assert.Equal(t, 0, apiResp.Code)
	assert.Equal(t, int64(1), apiResp.Data.Total)
	assert.Equal(t, 1, apiResp.Data.Page)
	assert.Equal(t, 20, apiResp.Data.PageSize)
	require.Len(t, apiResp.Data.Items, 1)
	assert.Equal(t, "web-server", apiResp.Data.Items[0].Name)
	repo.AssertExpectations(t)
}

func TestListIPAssetsRejectsInvalidExposure(t *testing.T) {
	gin.SetMode(gin.TestMode)

	repo := &caasmmocks.MockAssetRepository{}
	h := &Handler{assets: service.NewAssetService(repo, nil)}
	router := gin.New()
	router.GET("/caasm/ip-assets", h.ListIPAssets)

	req := httptest.NewRequest(http.MethodGet, "/caasm/ip-assets?exposure=internal", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	require.Equal(t, http.StatusOK, w.Code)
	var apiResp struct {
		Code    int    `json:"code"`
		Message string `json:"message"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &apiResp))
	assert.Equal(t, http.StatusBadRequest, apiResp.Code)
	assert.Contains(t, apiResp.Message, "exposure must be public or private")
	repo.AssertNotCalled(t, "ListIPAssets", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}
