package dbsync

import (
	"context"
	"fmt"
	"strings"
)

// SchemaTableSpec 本机 → 线上：空表/表结构 ensure 请求中的一张表。
type SchemaTableSpec struct {
	Table      string   `json:"table"`
	PKColumn   string   `json:"pk_column,omitempty"`
	Columns    []string `json:"columns"`
	OnlineDBID string   `json:"online_db_id,omitempty"`
}

// SchemaEnsureRequest agent 批量 ensure 空表到线上 A。
type SchemaEnsureRequest struct {
	OnlineDBID string            `json:"online_db_id"`
	Tables     []SchemaTableSpec `json:"tables"`
}

// SchemaEnsureItemResult 单表 ensure 结果。
type SchemaEnsureItemResult struct {
	Table   string `json:"table"`
	OK      bool   `json:"ok"`
	Created bool   `json:"created"` // true=本次新建；false=已存在跳过
	Message string `json:"message,omitempty"`
}

// SchemaEnsureResult 批量 ensure 响应。
type SchemaEnsureResult struct {
	OK      bool                     `json:"ok"`
	Results []SchemaEnsureItemResult `json:"results"`
	Message string                   `json:"message,omitempty"`
}

// SchemaDescribeRequest 从线上 A 拉取表结构（供本机建空表）。
type SchemaDescribeRequest struct {
	OnlineDBID string   `json:"online_db_id"`
	Tables     []string `json:"tables,omitempty"` // 空=全部业务表
}

// SchemaTableDesc 线上表结构描述。
type SchemaTableDesc struct {
	Name     string   `json:"name"`
	PKColumn string   `json:"pk_column"`
	Columns  []string `json:"columns"`
	RowCount int64    `json:"row_count"`
}

// SchemaDescribeResult 表结构列表。
type SchemaDescribeResult struct {
	OK      bool              `json:"ok"`
	Tables  []SchemaTableDesc `json:"tables"`
	Message string            `json:"message,omitempty"`
}

const MaxSchemaEnsureTables = 100

// EnsureSchemasOnRemote 在线上 A 为每张表 CREATE IF NOT EXISTS（空表也建）。
func EnsureSchemasOnRemote(ctx context.Context, ch *Channel, req SchemaEnsureRequest) (*SchemaEnsureResult, error) {
	if ch == nil {
		return nil, fmt.Errorf("channel is nil")
	}
	if len(req.Tables) == 0 {
		return nil, fmt.Errorf("tables required")
	}
	if len(req.Tables) > MaxSchemaEnsureTables {
		return nil, fmt.Errorf("tables limit %d", MaxSchemaEnsureTables)
	}
	db, err := AcquireRemote(ch.Remote.Driver, ch.Remote.DSN)
	if err != nil {
		return nil, wrapOpenRemote(err)
	}
	if err := EnsureMeta(ctx, db, ch.Remote.Driver); err != nil {
		return nil, Retryablef("ensure meta: %v", err)
	}

	out := &SchemaEnsureResult{OK: true, Results: make([]SchemaEnsureItemResult, 0, len(req.Tables))}
	for _, spec := range req.Tables {
		table := strings.TrimSpace(spec.Table)
		item := SchemaEnsureItemResult{Table: table}
		if table == "" {
			item.Message = "table required"
			out.OK = false
			out.Results = append(out.Results, item)
			continue
		}
		if !tableInChannel(ch, table) {
			item.Message = "table rejected"
			out.OK = false
			out.Results = append(out.Results, item)
			continue
		}
		pkCol := strings.TrimSpace(spec.PKColumn)
		if pkCol == "" && ch.PKColumns != nil && strings.TrimSpace(ch.PKColumns[table]) != "" {
			pkCol = ch.PKColumns[table]
		}
		if pkCol == "" {
			pkCol = "id"
		}
		existed, err := tableExists(ctx, db, ch.Remote.Driver, table)
		if err != nil {
			return nil, Retryablef("table exists: %v", err)
		}
		if err := EnsureTableFromColumns(ctx, db, ch.Remote.Driver, table, pkCol, spec.Columns); err != nil {
			item.Message = err.Error()
			out.OK = false
			out.Results = append(out.Results, item)
			continue
		}
		item.OK = true
		item.Created = !existed
		if existed {
			item.Message = "already exists"
		} else {
			item.Message = "created"
		}
		out.Results = append(out.Results, item)
	}
	out.Message = "empty tables ensured on online A; client should also create missing local tables from schema describe"
	return out, nil
}

// DescribeSchemasFromRemote 列出线上 A 业务表结构（含空表），供本机 CREATE IF NOT EXISTS。
func DescribeSchemasFromRemote(ctx context.Context, ch *Channel, req SchemaDescribeRequest) (*SchemaDescribeResult, error) {
	if ch == nil {
		return nil, fmt.Errorf("channel is nil")
	}
	db, err := AcquireRemote(ch.Remote.Driver, ch.Remote.DSN)
	if err != nil {
		return nil, wrapOpenRemote(err)
	}
	if err := EnsureMeta(ctx, db, ch.Remote.Driver); err != nil {
		return nil, Retryablef("ensure meta: %v", err)
	}

	want := map[string]struct{}{}
	for _, t := range req.Tables {
		t = strings.TrimSpace(t)
		if t != "" {
			want[t] = struct{}{}
		}
	}

	names, err := ListTables(ctx, db, ch.Remote.Driver)
	if err != nil {
		return nil, Retryablef("list tables: %v", err)
	}

	out := &SchemaDescribeResult{OK: true, Tables: make([]SchemaTableDesc, 0, len(names))}
	for _, name := range names {
		if len(want) > 0 {
			if _, ok := want[name]; !ok {
				continue
			}
		}
		if !tableInChannel(ch, name) {
			continue
		}
		pkCol := "id"
		if ch.PKColumns != nil && strings.TrimSpace(ch.PKColumns[name]) != "" {
			pkCol = ch.PKColumns[name]
		}
		cols, err := listColumns(ctx, db, ch.Remote.Driver, name)
		if err != nil {
			return nil, Retryablef("columns %s: %v", name, err)
		}
		n, _ := countRows(ctx, db, ch.Remote.Driver, name)
		out.Tables = append(out.Tables, SchemaTableDesc{
			Name:     name,
			PKColumn: pkCol,
			Columns:  cols,
			RowCount: n,
		})
	}
	if len(out.Tables) == 0 {
		out.Message = "online A has no business tables yet; push or ensure-schema first"
	} else {
		out.Message = "use columns to CREATE TABLE IF NOT EXISTS on local B (including empty tables)"
	}
	return out, nil
}
