package service

import (
	"context"
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"strings"

	"cmr-backend/internal/apperr"
	"cmr-backend/internal/platform/assets"
	"cmr-backend/internal/platform/security"
	"cmr-backend/internal/store/postgres"
)

type AdminAssetService struct {
	store          *postgres.Store
	assetBaseURL   string
	assetPublisher *assets.OSSUtilPublisher
}

type ManagedAssetSummary struct {
	ID              string         `json:"id"`
	AssetType       string         `json:"assetType"`
	AssetCode       string         `json:"assetCode"`
	Version         string         `json:"version"`
	Title           *string        `json:"title,omitempty"`
	SourceMode      string         `json:"sourceMode"`
	StorageProvider string         `json:"storageProvider"`
	ObjectKey       *string        `json:"objectKey,omitempty"`
	PublicURL       string         `json:"publicUrl"`
	FileName        *string        `json:"fileName,omitempty"`
	ContentType     *string        `json:"contentType,omitempty"`
	FileSizeBytes   *int64         `json:"fileSizeBytes,omitempty"`
	ChecksumSHA256  *string        `json:"checksumSha256,omitempty"`
	Status          string         `json:"status"`
	Metadata        map[string]any `json:"metadata,omitempty"`
}

type RegisterLinkAssetInput struct {
	AssetType   string         `json:"assetType"`
	AssetCode   string         `json:"assetCode"`
	Version     string         `json:"version"`
	Title       *string        `json:"title,omitempty"`
	PublicURL   string         `json:"publicUrl"`
	FileName    *string        `json:"fileName,omitempty"`
	ContentType *string        `json:"contentType,omitempty"`
	Status      string         `json:"status"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

type UploadAssetFileInput struct {
	AssetType   string
	AssetCode   string
	Version     string
	Title       *string
	ObjectDir   *string
	FileName    string
	ContentType string
	FileSize    int64
	Checksum    string
	TempPath    string
	Status      string
	Metadata    map[string]any
}

func NewAdminAssetService(store *postgres.Store, assetBaseURL string, assetPublisher *assets.OSSUtilPublisher) *AdminAssetService {
	return &AdminAssetService{
		store:          store,
		assetBaseURL:   strings.TrimRight(strings.TrimSpace(assetBaseURL), "/"),
		assetPublisher: assetPublisher,
	}
}

func (s *AdminAssetService) ListManagedAssets(ctx context.Context, limit int) ([]ManagedAssetSummary, error) {
	items, err := s.store.ListManagedAssets(ctx, limit)
	if err != nil {
		return nil, err
	}
	result := make([]ManagedAssetSummary, 0, len(items))
	for _, item := range items {
		result = append(result, buildManagedAssetSummary(item))
	}
	return result, nil
}

func (s *AdminAssetService) GetManagedAsset(ctx context.Context, assetPublicID string) (*ManagedAssetSummary, error) {
	record, err := s.store.GetManagedAssetByPublicID(ctx, strings.TrimSpace(assetPublicID))
	if err != nil {
		return nil, err
	}
	if record == nil {
		return nil, apperr.New(http.StatusNotFound, "asset_not_found", "asset not found")
	}
	summary := buildManagedAssetSummary(*record)
	return &summary, nil
}

func (s *AdminAssetService) RegisterExternalLink(ctx context.Context, input RegisterLinkAssetInput) (*ManagedAssetSummary, error) {
	if err := validateManagedAssetInput(input.AssetType, input.AssetCode, input.Version); err != nil {
		return nil, err
	}
	publicURL := strings.TrimSpace(input.PublicURL)
	if publicURL == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "publicUrl is required")
	}

	publicID, err := security.GeneratePublicID("asset")
	if err != nil {
		return nil, err
	}

	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	record, err := s.store.CreateManagedAsset(ctx, tx, postgres.CreateManagedAssetParams{
		PublicID:        publicID,
		AssetType:       normalizeCode(input.AssetType),
		AssetCode:       normalizeCode(input.AssetCode),
		Version:         strings.TrimSpace(input.Version),
		Title:           assetTrimStringPtr(input.Title),
		SourceMode:      "external_link",
		StorageProvider: "external",
		ObjectKey:       nil,
		PublicURL:       publicURL,
		FileName:        assetTrimStringPtr(input.FileName),
		ContentType:     assetTrimStringPtr(input.ContentType),
		FileSizeBytes:   nil,
		ChecksumSHA256:  nil,
		Status:          normalizeManagedAssetStatus(input.Status),
		MetadataJSONB:   normalizeJSONMap(input.Metadata),
	})
	if err != nil {
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	summary := buildManagedAssetSummary(*record)
	return &summary, nil
}

func (s *AdminAssetService) UploadAssetFile(ctx context.Context, input UploadAssetFileInput) (*ManagedAssetSummary, error) {
	if err := validateManagedAssetInput(input.AssetType, input.AssetCode, input.Version); err != nil {
		return nil, err
	}
	if !s.assetPublisher.Enabled() {
		return nil, apperr.New(http.StatusFailedDependency, "asset_publisher_not_configured", "asset publisher is not configured")
	}
	if strings.TrimSpace(input.TempPath) == "" || strings.TrimSpace(input.FileName) == "" {
		return nil, apperr.New(http.StatusBadRequest, "invalid_params", "upload file is required")
	}

	objectDir := s.defaultObjectDir(input.AssetType, input.AssetCode, input.Version, input.ObjectDir)
	publicURL := s.assetBaseURL + "/" + strings.TrimLeft(path.Join(objectDir, sanitizeFileName(input.FileName)), "/")

	if err := s.assetPublisher.UploadFile(ctx, publicURL, input.TempPath); err != nil {
		return nil, err
	}

	publicID, err := security.GeneratePublicID("asset")
	if err != nil {
		return nil, err
	}

	objectKey := strings.TrimPrefix(strings.TrimPrefix(publicURL, s.assetBaseURL), "/")
	fileName := sanitizeFileName(input.FileName)
	contentType := detectContentType(fileName, input.ContentType)
	checksum := strings.TrimSpace(input.Checksum)

	tx, err := s.store.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	record, err := s.store.CreateManagedAsset(ctx, tx, postgres.CreateManagedAssetParams{
		PublicID:        publicID,
		AssetType:       normalizeCode(input.AssetType),
		AssetCode:       normalizeCode(input.AssetCode),
		Version:         strings.TrimSpace(input.Version),
		Title:           assetTrimStringPtr(input.Title),
		SourceMode:      "uploaded",
		StorageProvider: "oss",
		ObjectKey:       stringPtr(objectKey),
		PublicURL:       publicURL,
		FileName:        stringPtr(fileName),
		ContentType:     stringPtr(contentType),
		FileSizeBytes:   &input.FileSize,
		ChecksumSHA256:  stringPtr(checksum),
		Status:          normalizeManagedAssetStatus(input.Status),
		MetadataJSONB:   normalizeJSONMap(input.Metadata),
	})
	if err != nil {
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	summary := buildManagedAssetSummary(*record)
	return &summary, nil
}

func (s *AdminAssetService) defaultObjectDir(assetType, assetCode, version string, preferred *string) string {
	if preferred != nil && strings.TrimSpace(*preferred) != "" {
		return strings.Trim(strings.ReplaceAll(strings.TrimSpace(*preferred), "\\", "/"), "/")
	}
	return path.Join("uploads", normalizeCode(assetType), normalizeCode(assetCode), strings.TrimSpace(version))
}

func buildManagedAssetSummary(record postgres.ManagedAssetRecord) ManagedAssetSummary {
	return ManagedAssetSummary{
		ID:              record.PublicID,
		AssetType:       record.AssetType,
		AssetCode:       record.AssetCode,
		Version:         record.Version,
		Title:           record.Title,
		SourceMode:      record.SourceMode,
		StorageProvider: record.StorageProvider,
		ObjectKey:       record.ObjectKey,
		PublicURL:       record.PublicURL,
		FileName:        record.FileName,
		ContentType:     record.ContentType,
		FileSizeBytes:   record.FileSizeBytes,
		ChecksumSHA256:  record.ChecksumSHA256,
		Status:          record.Status,
		Metadata:        normalizeJSONMap(record.MetadataJSONB),
	}
}

func validateManagedAssetInput(assetType, assetCode, version string) error {
	if normalizeCode(assetType) == "" || normalizeCode(assetCode) == "" || strings.TrimSpace(version) == "" {
		return apperr.New(http.StatusBadRequest, "invalid_params", "assetType, assetCode and version are required")
	}
	return nil
}

func normalizeManagedAssetStatus(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", "active":
		return "active"
	case "draft", "disabled", "archived":
		return strings.ToLower(strings.TrimSpace(value))
	default:
		return "active"
	}
}

func normalizeCode(value string) string {
	value = strings.TrimSpace(strings.ToLower(value))
	value = strings.ReplaceAll(value, " ", "-")
	return value
}

func sanitizeFileName(name string) string {
	name = filepath.Base(strings.TrimSpace(name))
	name = strings.ReplaceAll(name, " ", "-")
	return name
}

func detectContentType(fileName, provided string) string {
	if strings.TrimSpace(provided) != "" {
		return strings.TrimSpace(provided)
	}
	if ext := filepath.Ext(fileName); ext != "" {
		if guessed := mime.TypeByExtension(ext); guessed != "" {
			return guessed
		}
	}
	return "application/octet-stream"
}

func stringPtr(value string) *string {
	value = strings.TrimSpace(value)
	if value == "" {
		return nil
	}
	return &value
}

func assetTrimStringPtr(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}

func normalizeJSONMap(value map[string]any) map[string]any {
	if value == nil {
		return map[string]any{}
	}
	return value
}
