package store

import (
	"context"
	"encoding/json"
	"strings"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
)

type PortraitAsset struct {
	ID                    string         `json:"id"`
	GatewayUserID         string         `json:"gatewayUserId,omitempty"`
	UserID                string         `json:"userId"`
	GatewayTenantID       string         `json:"gatewayTenantId,omitempty"`
	TenantID              string         `json:"tenantId,omitempty"`
	TenantKey             string         `json:"tenantKey,omitempty"`
	Name                  string         `json:"name"`
	Description           string         `json:"description,omitempty"`
	SourceType            string         `json:"sourceType"`
	URL                   string         `json:"url"`
	Preview               string         `json:"preview,omitempty"`
	MimeType              string         `json:"mimeType,omitempty"`
	ByteSize              int64          `json:"size,omitempty"`
	SourceSHA256          string         `json:"sourceSha256,omitempty"`
	PrivateAvatarEligible bool           `json:"privateAvatarEligible"`
	Status                string         `json:"status"`
	LastError             string         `json:"lastError,omitempty"`
	Metadata              map[string]any `json:"metadata,omitempty"`
	CreatedAt             time.Time      `json:"createdAt"`
	UpdatedAt             time.Time      `json:"updatedAt"`
}

type PortraitAssetBinding struct {
	ID               string    `json:"id"`
	AssetID          string    `json:"assetId"`
	PlatformID       string    `json:"platformId"`
	ProjectName      string    `json:"projectName,omitempty"`
	AssetGroupID     string    `json:"assetGroupId,omitempty"`
	RemoteAssetID    string    `json:"remoteAssetId,omitempty"`
	RemoteAssetURI   string    `json:"remoteAssetUri,omitempty"`
	Status           string    `json:"status"`
	LastErrorCode    string    `json:"lastErrorCode,omitempty"`
	LastErrorMessage string    `json:"lastErrorMessage,omitempty"`
	LastSyncedAt     string    `json:"lastSyncedAt,omitempty"`
	CreatedAt        time.Time `json:"createdAt"`
	UpdatedAt        time.Time `json:"updatedAt"`
}

type PortraitAssetInput struct {
	GatewayUserID         string
	UserID                string
	GatewayTenantID       string
	TenantID              string
	TenantKey             string
	Name                  string
	Description           string
	SourceType            string
	URL                   string
	Preview               string
	MimeType              string
	ByteSize              int64
	SourceSHA256          string
	PrivateAvatarEligible bool
	Metadata              map[string]any
}

type PortraitAssetListFilter struct {
	Keyword    string
	SourceType string
	Page       int
	PageSize   int
}

type PortraitAssetListResult struct {
	Items    []PortraitAsset
	Total    int
	Page     int
	PageSize int
}

type PortraitAssetPlatform struct {
	PlatformID  string
	PlatformKey string
	Provider    string
	Credentials map[string]any
	Config      map[string]any
}

const portraitAssetColumns = `
a.id::text, COALESCE(a.gateway_user_id::text, ''), a.user_id,
COALESCE(a.gateway_tenant_id::text, ''), COALESCE(a.tenant_id, ''), COALESCE(a.tenant_key, ''),
a.name, a.description, a.source_type, a.url, a.preview, a.mime_type, a.byte_size,
a.source_sha256, a.private_avatar_eligible, a.status, a.last_error, a.metadata, a.created_at, a.updated_at`

func (s *Store) CreatePortraitAsset(ctx context.Context, input PortraitAssetInput) (PortraitAsset, error) {
	metadata, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
	return scanPortraitAsset(s.pool.QueryRow(ctx, `
INSERT INTO gateway_portrait_assets (
  gateway_user_id, user_id, gateway_tenant_id, tenant_id, tenant_key,
  name, description, source_type, url, preview, mime_type, byte_size, source_sha256,
  private_avatar_eligible, status, metadata
)
VALUES (
  NULLIF($1, '')::uuid, $2, NULLIF($3, '')::uuid, NULLIF($4, ''), NULLIF($5, ''),
  $6, $7, $8, $9, $10, $11, $12, $13, $14, 'not_synced', $15::jsonb
)
RETURNING `+portraitAssetColumns,
		input.GatewayUserID, input.UserID, input.GatewayTenantID, input.TenantID, input.TenantKey,
		strings.TrimSpace(input.Name), strings.TrimSpace(input.Description), strings.TrimSpace(input.SourceType),
		strings.TrimSpace(input.URL), strings.TrimSpace(input.Preview), strings.TrimSpace(input.MimeType), input.ByteSize,
		strings.TrimSpace(input.SourceSHA256), input.PrivateAvatarEligible, string(metadata),
	))
}

func (s *Store) FindPortraitAssetBySourceHash(ctx context.Context, user *auth.User, sourceSHA256 string) (PortraitAsset, bool, error) {
	sourceSHA256 = strings.TrimSpace(sourceSHA256)
	if sourceSHA256 == "" {
		return PortraitAsset{}, false, nil
	}
	gatewayUserID, userID := portraitAssetUserKeys(user)
	asset, err := scanPortraitAsset(s.pool.QueryRow(ctx, `
SELECT `+portraitAssetColumns+`
FROM gateway_portrait_assets a
WHERE ((NULLIF($1, '')::uuid IS NOT NULL AND a.gateway_user_id = NULLIF($1, '')::uuid)
    OR (NULLIF($2, '') IS NOT NULL AND a.user_id = $2))
  AND a.source_sha256 = $3
ORDER BY a.created_at DESC
LIMIT 1`, gatewayUserID, userID, sourceSHA256))
	if IsNotFound(err) {
		return PortraitAsset{}, false, nil
	}
	return asset, err == nil, err
}

func (s *Store) FindPortraitAssetForUser(ctx context.Context, user *auth.User, assetID string) (PortraitAsset, bool, error) {
	assetID = strings.TrimSpace(assetID)
	if assetID == "" {
		return PortraitAsset{}, false, nil
	}
	gatewayUserID, userID := portraitAssetUserKeys(user)
	asset, err := scanPortraitAsset(s.pool.QueryRow(ctx, `
SELECT `+portraitAssetColumns+`
FROM gateway_portrait_assets a
WHERE a.id = NULLIF($3, '')::uuid
  AND ((NULLIF($1, '')::uuid IS NOT NULL AND a.gateway_user_id = NULLIF($1, '')::uuid)
    OR (NULLIF($2, '') IS NOT NULL AND a.user_id = $2))`, gatewayUserID, userID, assetID))
	if IsNotFound(err) {
		return PortraitAsset{}, false, nil
	}
	return asset, err == nil, err
}

func (s *Store) ListPortraitAssets(ctx context.Context, user *auth.User, filter PortraitAssetListFilter) (PortraitAssetListResult, error) {
	page := filter.Page
	if page < 1 {
		page = 1
	}
	pageSize := filter.PageSize
	if pageSize < 1 {
		pageSize = 20
	}
	if pageSize > 100 {
		pageSize = 100
	}
	gatewayUserID, userID := portraitAssetUserKeys(user)
	keyword := strings.TrimSpace(filter.Keyword)
	if keyword != "" {
		keyword = "%" + keyword + "%"
	}
	where := `
WHERE ((NULLIF($1, '')::uuid IS NOT NULL AND a.gateway_user_id = NULLIF($1, '')::uuid)
    OR (NULLIF($2, '') IS NOT NULL AND a.user_id = $2))
  AND (NULLIF($3, '') IS NULL OR a.source_type = $3)
  AND (NULLIF($4, '') IS NULL OR a.name ILIKE $4 OR a.description ILIKE $4)`
	args := []any{gatewayUserID, userID, strings.TrimSpace(filter.SourceType), keyword}
	var total int
	if err := s.pool.QueryRow(ctx, `SELECT count(*) FROM gateway_portrait_assets a `+where, args...).Scan(&total); err != nil {
		return PortraitAssetListResult{}, err
	}
	args = append(args, pageSize, (page-1)*pageSize)
	rows, err := s.pool.Query(ctx, `SELECT `+portraitAssetColumns+`
FROM gateway_portrait_assets a `+where+`
ORDER BY a.created_at DESC
LIMIT $5 OFFSET $6`, args...)
	if err != nil {
		return PortraitAssetListResult{}, err
	}
	defer rows.Close()
	items := make([]PortraitAsset, 0)
	for rows.Next() {
		asset, err := scanPortraitAsset(rows)
		if err != nil {
			return PortraitAssetListResult{}, err
		}
		items = append(items, asset)
	}
	if err := rows.Err(); err != nil {
		return PortraitAssetListResult{}, err
	}
	return PortraitAssetListResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Store) GetPortraitAssetBinding(ctx context.Context, assetID string, platformID string) (PortraitAssetBinding, bool, error) {
	binding, err := scanPortraitAssetBinding(s.pool.QueryRow(ctx, `
SELECT id::text, asset_id::text, platform_id::text, project_name, asset_group_id,
       remote_asset_id, remote_asset_uri, status, last_error_code, last_error_message,
       COALESCE(last_synced_at::text, ''), created_at, updated_at
FROM gateway_portrait_asset_bindings
WHERE asset_id = $1::uuid AND platform_id = $2::uuid`, assetID, platformID))
	if IsNotFound(err) {
		return PortraitAssetBinding{}, false, nil
	}
	return binding, err == nil, err
}

func (s *Store) UpsertPortraitAssetBinding(ctx context.Context, binding PortraitAssetBinding) (PortraitAssetBinding, error) {
	return scanPortraitAssetBinding(s.pool.QueryRow(ctx, `
INSERT INTO gateway_portrait_asset_bindings (
  asset_id, platform_id, project_name, asset_group_id, remote_asset_id, remote_asset_uri,
  status, last_error_code, last_error_message, last_synced_at
)
VALUES ($1::uuid, $2::uuid, $3, $4, $5, $6, $7, $8, $9, now())
ON CONFLICT (asset_id, platform_id) DO UPDATE SET
  project_name = EXCLUDED.project_name,
  asset_group_id = EXCLUDED.asset_group_id,
  remote_asset_id = CASE WHEN EXCLUDED.remote_asset_id <> '' THEN EXCLUDED.remote_asset_id ELSE gateway_portrait_asset_bindings.remote_asset_id END,
  remote_asset_uri = CASE WHEN EXCLUDED.remote_asset_uri <> '' THEN EXCLUDED.remote_asset_uri ELSE gateway_portrait_asset_bindings.remote_asset_uri END,
  status = EXCLUDED.status,
  last_error_code = EXCLUDED.last_error_code,
  last_error_message = EXCLUDED.last_error_message,
  last_synced_at = now(),
  updated_at = now()
RETURNING id::text, asset_id::text, platform_id::text, project_name, asset_group_id,
          remote_asset_id, remote_asset_uri, status, last_error_code, last_error_message,
          COALESCE(last_synced_at::text, ''), created_at, updated_at`,
		binding.AssetID, binding.PlatformID, strings.TrimSpace(binding.ProjectName), strings.TrimSpace(binding.AssetGroupID),
		strings.TrimSpace(binding.RemoteAssetID), strings.TrimSpace(binding.RemoteAssetURI), strings.TrimSpace(binding.Status),
		strings.TrimSpace(binding.LastErrorCode), strings.TrimSpace(binding.LastErrorMessage),
	))
}

func (s *Store) UpdatePortraitAssetStatus(ctx context.Context, assetID string, status string, lastError string) error {
	_, err := s.pool.Exec(ctx, `
UPDATE gateway_portrait_assets
SET status = $2, last_error = $3, updated_at = now()
WHERE id = $1::uuid`, assetID, strings.TrimSpace(status), strings.TrimSpace(lastError))
	return err
}

func (s *Store) PortraitAssetBindingSummary(ctx context.Context, assetID string) (active int, total int, latestError string, updatedAt string, err error) {
	err = s.pool.QueryRow(ctx, `
SELECT COUNT(*) FILTER (WHERE status = 'active'), COUNT(*),
       COALESCE((ARRAY_AGG(NULLIF(last_error_message, '') ORDER BY updated_at DESC) FILTER (WHERE NULLIF(last_error_message, '') IS NOT NULL))[1], ''),
       COALESCE(MAX(updated_at)::text, '')
FROM gateway_portrait_asset_bindings
WHERE asset_id = $1::uuid`, assetID).Scan(&active, &total, &latestError, &updatedAt)
	return
}

func (s *Store) ListPortraitAssetPlatforms(ctx context.Context) ([]PortraitAssetPlatform, error) {
	rows, err := s.pool.Query(ctx, `
SELECT p.id::text, p.platform_key, p.provider, p.credentials, p.config
FROM integration_platforms p
WHERE p.deleted_at IS NULL
  AND p.status = 'enabled'
  AND LOWER(p.provider) IN ('volces', 'volces-openai')
ORDER BY COALESCE(p.dynamic_priority, p.priority), p.created_at`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := make([]PortraitAssetPlatform, 0)
	for rows.Next() {
		var item PortraitAssetPlatform
		var credentials, config []byte
		if err := rows.Scan(&item.PlatformID, &item.PlatformKey, &item.Provider, &credentials, &config); err != nil {
			return nil, err
		}
		item.Credentials = decodeObject(credentials)
		item.Config = decodeObject(config)
		items = append(items, item)
	}
	return items, rows.Err()
}

func portraitAssetUserKeys(user *auth.User) (string, string) {
	if user == nil {
		return "", ""
	}
	gatewayUserID := strings.TrimSpace(user.GatewayUserID)
	if gatewayUserID == "" && user.Source == "gateway" {
		gatewayUserID = strings.TrimSpace(user.ID)
	}
	return gatewayUserID, strings.TrimSpace(user.ID)
}

type portraitAssetScanner interface{ Scan(dest ...any) error }

func scanPortraitAsset(scanner portraitAssetScanner) (PortraitAsset, error) {
	var asset PortraitAsset
	var metadata []byte
	err := scanner.Scan(
		&asset.ID, &asset.GatewayUserID, &asset.UserID, &asset.GatewayTenantID, &asset.TenantID, &asset.TenantKey,
		&asset.Name, &asset.Description, &asset.SourceType, &asset.URL, &asset.Preview, &asset.MimeType, &asset.ByteSize,
		&asset.SourceSHA256, &asset.PrivateAvatarEligible, &asset.Status, &asset.LastError, &metadata, &asset.CreatedAt, &asset.UpdatedAt,
	)
	if err != nil {
		return PortraitAsset{}, err
	}
	asset.Metadata = decodeObject(metadata)
	return asset, nil
}

func scanPortraitAssetBinding(scanner portraitAssetScanner) (PortraitAssetBinding, error) {
	var binding PortraitAssetBinding
	if err := scanner.Scan(
		&binding.ID, &binding.AssetID, &binding.PlatformID, &binding.ProjectName, &binding.AssetGroupID,
		&binding.RemoteAssetID, &binding.RemoteAssetURI, &binding.Status, &binding.LastErrorCode, &binding.LastErrorMessage,
		&binding.LastSyncedAt, &binding.CreatedAt, &binding.UpdatedAt,
	); err != nil {
		return PortraitAssetBinding{}, err
	}
	return binding, nil
}
