package store

import (
	"context"
	"database/sql"
	"time"

	"github.com/jackc/pgx/v5"
)

type RequestAsset struct {
	ID                string     `json:"id"`
	SHA256            string     `json:"sha256"`
	ContentType       string     `json:"contentType"`
	ByteSize          int64      `json:"byteSize"`
	URL               string     `json:"url"`
	StorageProvider   string     `json:"storageProvider"`
	StorageChannelID  string     `json:"storageChannelId,omitempty"`
	StorageChannelKey string     `json:"storageChannelKey,omitempty"`
	ObjectKey         string     `json:"objectKey,omitempty"`
	AccessScope       string     `json:"accessScope,omitempty"`
	LocalPath         string     `json:"localPath,omitempty"`
	ExpiresAt         *time.Time `json:"expiresAt,omitempty"`
	ExpiredAt         *time.Time `json:"expiredAt,omitempty"`
	RefCount          int        `json:"refCount"`
	CreatedAt         time.Time  `json:"createdAt"`
	UpdatedAt         time.Time  `json:"updatedAt"`
}

type RequestAssetInput struct {
	SHA256            string
	ContentType       string
	ByteSize          int64
	URL               string
	StorageProvider   string
	StorageChannelID  string
	StorageChannelKey string
	ObjectKey         string
	AccessScope       string
	LocalPath         string
	ExpiresAt         *time.Time
}

func (s *Store) FindRequestAsset(ctx context.Context, sha256 string, contentType string) (RequestAsset, bool, error) {
	asset, err := scanRequestAsset(s.pool.QueryRow(ctx, `
SELECT id::text, sha256, content_type, byte_size, url, storage_provider,
       COALESCE(storage_channel_id::text, ''), COALESCE(storage_channel_key, ''),
       COALESCE(object_key, ''), COALESCE(access_scope, 'private'),
       COALESCE(local_path, ''), expires_at, expired_at, ref_count, created_at, updated_at
FROM gateway_request_assets
WHERE sha256 = $1 AND content_type = $2`, sha256, contentType))
	if err != nil {
		if err == pgx.ErrNoRows {
			return RequestAsset{}, false, nil
		}
		return RequestAsset{}, false, err
	}
	return asset, true, nil
}

func (s *Store) UpsertRequestAsset(ctx context.Context, input RequestAssetInput) (RequestAsset, error) {
	return scanRequestAsset(s.pool.QueryRow(ctx, `
INSERT INTO gateway_request_assets (
  sha256, content_type, byte_size, url, storage_provider, storage_channel_id,
  storage_channel_key, object_key, access_scope, local_path, expires_at, expired_at, ref_count
)
VALUES ($1, $2, $3, $4, $5, NULLIF($6, '')::uuid, NULLIF($7, ''), NULLIF($8, ''),
        COALESCE(NULLIF($9, ''), 'private'), NULLIF($10, ''), $11, NULL, 1)
ON CONFLICT (sha256, content_type) DO UPDATE
SET byte_size = EXCLUDED.byte_size,
    url = EXCLUDED.url,
    storage_provider = EXCLUDED.storage_provider,
    storage_channel_id = EXCLUDED.storage_channel_id,
    storage_channel_key = EXCLUDED.storage_channel_key,
    object_key = EXCLUDED.object_key,
    access_scope = EXCLUDED.access_scope,
    local_path = EXCLUDED.local_path,
    expires_at = EXCLUDED.expires_at,
    expired_at = NULL,
    ref_count = gateway_request_assets.ref_count + 1,
    updated_at = now()
RETURNING id::text, sha256, content_type, byte_size, url, storage_provider,
          COALESCE(storage_channel_id::text, ''), COALESCE(storage_channel_key, ''),
          COALESCE(object_key, ''), COALESCE(access_scope, 'private'),
          COALESCE(local_path, ''), expires_at, expired_at, ref_count, created_at, updated_at`,
		input.SHA256,
		input.ContentType,
		input.ByteSize,
		input.URL,
		input.StorageProvider,
		input.StorageChannelID,
		input.StorageChannelKey,
		input.ObjectKey,
		input.AccessScope,
		input.LocalPath,
		input.ExpiresAt,
	))
}

func (s *Store) IncrementRequestAssetRefCount(ctx context.Context, sha256 string, contentType string) error {
	_, err := s.pool.Exec(ctx, `
UPDATE gateway_request_assets
SET ref_count = ref_count + 1,
    updated_at = now()
WHERE sha256 = $1 AND content_type = $2`, sha256, contentType)
	return err
}

func (s *Store) MarkRequestAssetExpiredByLocalPath(ctx context.Context, localPath string, expiredAt time.Time) error {
	if localPath == "" {
		return nil
	}
	_, err := s.pool.Exec(ctx, `
UPDATE gateway_request_assets
SET expired_at = COALESCE(expired_at, $2),
    updated_at = now()
WHERE local_path = $1
  AND storage_provider = 'local_static'
  AND expired_at IS NULL`, localPath, expiredAt)
	return err
}

func scanRequestAsset(scanner interface{ Scan(dest ...any) error }) (RequestAsset, error) {
	var asset RequestAsset
	var localPath string
	var expiresAt sql.NullTime
	var expiredAt sql.NullTime
	if err := scanner.Scan(
		&asset.ID,
		&asset.SHA256,
		&asset.ContentType,
		&asset.ByteSize,
		&asset.URL,
		&asset.StorageProvider,
		&asset.StorageChannelID,
		&asset.StorageChannelKey,
		&asset.ObjectKey,
		&asset.AccessScope,
		&localPath,
		&expiresAt,
		&expiredAt,
		&asset.RefCount,
		&asset.CreatedAt,
		&asset.UpdatedAt,
	); err != nil {
		return RequestAsset{}, err
	}
	asset.LocalPath = localPath
	if expiresAt.Valid {
		asset.ExpiresAt = &expiresAt.Time
	}
	if expiredAt.Valid {
		asset.ExpiredAt = &expiredAt.Time
	}
	return asset, nil
}
