package meta

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"aijianzhan/platform/internal/blueprint"

	"github.com/lib/pq"
)

type PostgresStore struct {
	DB *sql.DB
}

func NewPostgresStore(db *sql.DB) *PostgresStore {
	return &PostgresStore{DB: db}
}

func (s *PostgresStore) GetBySlug(ctx context.Context, tenantID int64, slug string) (*AppRecord, error) {
	const q = `
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
       blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
FROM platform_meta.tenant_apps
WHERE tenant_id = $1 AND slug = $2
LIMIT 1`
	row := s.DB.QueryRowContext(ctx, q, tenantID, slug)
	rec, err := scanApp(row)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, fmt.Errorf("app not found")
	}
	return rec, err
}

func (s *PostgresStore) FindPublishedBySlug(ctx context.Context, slug string) (*AppRecord, error) {
	const q = `
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
       blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
FROM platform_meta.tenant_apps
WHERE slug = $1 AND status = $2
ORDER BY updated_at DESC
LIMIT 1`
	row := s.DB.QueryRowContext(ctx, q, slug, string(StatusPublished))
	rec, err := scanApp(row)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, fmt.Errorf("app not found")
	}
	return rec, err
}

func (s *PostgresStore) ListByTenant(ctx context.Context, tenantID int64) ([]AppSummary, error) {
	const q = `
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
       blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
FROM platform_meta.tenant_apps
WHERE tenant_id = $1
ORDER BY updated_at DESC`
	rows, err := s.DB.QueryContext(ctx, q, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := make([]AppSummary, 0)
	for rows.Next() {
		rec, err := scanApp(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, summarizeApp(rec))
	}
	return out, rows.Err()
}

func (s *PostgresStore) ListPublishedApps(ctx context.Context) ([]*AppRecord, error) {
	const q = `
SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status,
       blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
FROM platform_meta.tenant_apps
WHERE status = $1
ORDER BY tenant_id, slug`
	rows, err := s.DB.QueryContext(ctx, q, string(StatusPublished))
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := make([]*AppRecord, 0)
	for rows.Next() {
		rec, err := scanApp(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, rec)
	}
	return out, rows.Err()
}

func (s *PostgresStore) Save(ctx context.Context, app *AppRecord) error {
	if app == nil {
		return fmt.Errorf("app is nil")
	}
	if app.Blueprint == nil {
		return fmt.Errorf("blueprint is nil")
	}
	bpRaw, err := json.Marshal(app.Blueprint)
	if err != nil {
		return err
	}
	ddlRaw, err := json.Marshal(app.DDL)
	if err != nil {
		return err
	}
	epRaw, err := json.Marshal(app.Endpoints)
	if err != nil {
		return err
	}
	if app.CreatedAt.IsZero() {
		app.CreatedAt = time.Now().UTC()
	}
	app.UpdatedAt = time.Now().UTC()

	const q = `
INSERT INTO platform_meta.tenant_apps (
    app_id, tenant_id, slug, name, schema_name, database_name, engine, status,
    blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at
) VALUES (
    $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14
)
ON CONFLICT (tenant_id, slug) DO UPDATE SET
    name = EXCLUDED.name,
    schema_name = EXCLUDED.schema_name,
    database_name = EXCLUDED.database_name,
    engine = EXCLUDED.engine,
    status = EXCLUDED.status,
    blueprint_json = EXCLUDED.blueprint_json,
    ddl_json = EXCLUDED.ddl_json,
    endpoints_json = EXCLUDED.endpoints_json,
    error_msg = EXCLUDED.error_msg,
    updated_at = EXCLUDED.updated_at,
    app_id = platform_meta.tenant_apps.app_id,
    created_at = platform_meta.tenant_apps.created_at
RETURNING app_id, created_at, updated_at`

	err = s.DB.QueryRowContext(ctx, q,
		app.AppID,
		app.TenantID,
		app.Slug,
		app.Name,
		app.SchemaName,
		app.DatabaseName,
		app.Engine,
		string(app.Status),
		bpRaw,
		ddlRaw,
		epRaw,
		app.Error,
		app.CreatedAt,
		app.UpdatedAt,
	).Scan(&app.AppID, &app.CreatedAt, &app.UpdatedAt)
	if err != nil {
		return fmt.Errorf("meta save: %w", err)
	}
	return nil
}

func (s *PostgresStore) ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error) {
	app, err := s.GetBySlug(ctx, tenantID, slug)
	if err != nil {
		return nil, err
	}
	return resolveResource(app, resource)
}

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

func scanApp(row scannable) (*AppRecord, error) {
	var (
		rec                  AppRecord
		status               string
		bpRaw, ddlRaw, epRaw []byte
		errMsg               sql.NullString
	)
	err := row.Scan(
		&rec.AppID,
		&rec.TenantID,
		&rec.Slug,
		&rec.Name,
		&rec.SchemaName,
		&rec.DatabaseName,
		&rec.Engine,
		&status,
		&bpRaw,
		&ddlRaw,
		&epRaw,
		&errMsg,
		&rec.CreatedAt,
		&rec.UpdatedAt,
	)
	if err != nil {
		return nil, err
	}
	rec.Status = AppStatus(status)
	if errMsg.Valid {
		rec.Error = errMsg.String
	}
	var bp blueprint.Blueprint
	if err := json.Unmarshal(bpRaw, &bp); err != nil {
		return nil, fmt.Errorf("blueprint json: %w", err)
	}
	rec.Blueprint = &bp
	if len(ddlRaw) > 0 {
		_ = json.Unmarshal(ddlRaw, &rec.DDL)
	}
	if len(epRaw) > 0 {
		_ = json.Unmarshal(epRaw, &rec.Endpoints)
	}
	return &rec, nil
}

// IsUniqueViolation 便于上层识别冲突（预留）。
func IsUniqueViolation(err error) bool {
	var pqErr *pq.Error
	return errors.As(err, &pqErr) && pqErr.Code == "23505"
}
