// Package mysql stores cron-service scheduler leases and run history.
package mysql

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

	_ "github.com/go-sql-driver/mysql"
	"hyapp/pkg/xerr"
)

// Repository owns cron-service metadata only; business data stays in owner services.
type Repository struct {
	db *sql.DB
}

// TaskRun is the durable audit row for one scheduled batch attempt.
type TaskRun struct {
	RunID          string
	TaskName       string
	AppCode        string
	OwnerNodeID    string
	Status         string
	ClaimedCount   int
	ProcessedCount int
	SuccessCount   int
	FailureCount   int
	StartedAtMs    int64
	FinishedAtMs   int64
	DurationMs     int64
	ErrorMessage   string
}

// Open creates the cron metadata connection pool and verifies connectivity.
func Open(ctx context.Context, dsn string) (*Repository, error) {
	if strings.TrimSpace(dsn) == "" {
		return nil, xerr.New(xerr.InvalidArgument, "mysql_dsn is required")
	}
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}
	// database/sql defaults to unlimited connections; cap the pool so bursts
	// cannot exhaust MySQL max_connections.
	db.SetMaxOpenConns(20)
	db.SetMaxIdleConns(10)
	db.SetConnMaxLifetime(30 * time.Minute)
	if err := db.PingContext(ctx); err != nil {
		_ = db.Close()
		return nil, err
	}
	return &Repository{db: db}, nil
}

// Close releases the cron metadata connection pool.
func (r *Repository) Close() error {
	if r == nil || r.db == nil {
		return nil
	}
	return r.db.Close()
}

// Ping verifies that cron metadata storage is currently reachable.
func (r *Repository) Ping(ctx context.Context) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "cron repository is not configured")
	}
	return r.db.PingContext(ctx)
}

// TryAcquireTaskLease serializes schedule triggers per task and app_code.
func (r *Repository) TryAcquireTaskLease(ctx context.Context, taskName string, appCode string, ownerNodeID string, nowMs int64, leaseTTL time.Duration) (bool, error) {
	if r == nil || r.db == nil {
		return false, xerr.New(xerr.Unavailable, "cron repository is not configured")
	}
	if leaseTTL <= 0 {
		leaseTTL = 30 * time.Second
	}

	leaseUntilMs := nowMs + leaseTTL.Milliseconds()
	result, err := r.db.ExecContext(ctx, `
		INSERT IGNORE INTO cron_task_leases (task_name, app_code, owner_node_id, lease_until_ms, updated_at_ms)
		VALUES (?, ?, ?, ?, ?)
	`, taskName, appCode, ownerNodeID, leaseUntilMs, nowMs)
	if err != nil {
		return false, err
	}
	inserted, err := result.RowsAffected()
	if err != nil {
		return false, err
	}
	if inserted > 0 {
		return true, nil
	}

	result, err = r.db.ExecContext(ctx, `
		UPDATE cron_task_leases
		SET owner_node_id = ?, lease_until_ms = ?, updated_at_ms = ?
		WHERE task_name = ? AND app_code = ? AND (lease_until_ms <= ? OR owner_node_id = ?)
	`, ownerNodeID, leaseUntilMs, nowMs, taskName, appCode, nowMs, ownerNodeID)
	if err != nil {
		return false, err
	}
	updated, err := result.RowsAffected()
	if err != nil {
		return false, err
	}
	return updated > 0, nil
}

// StartTaskRun inserts a running audit row before calling the owner service.
func (r *Repository) StartTaskRun(ctx context.Context, run TaskRun) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "cron repository is not configured")
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO cron_task_runs (
			run_id, task_name, app_code, owner_node_id, status,
			claimed_count, processed_count, success_count, failure_count,
			started_at_ms, finished_at_ms, duration_ms, error_message
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, '')
	`, run.RunID, run.TaskName, run.AppCode, run.OwnerNodeID, run.Status, run.ClaimedCount, run.ProcessedCount, run.SuccessCount, run.FailureCount, run.StartedAtMs)
	return err
}

// FinishTaskRun records the final outcome for one scheduled batch attempt.
func (r *Repository) FinishTaskRun(ctx context.Context, run TaskRun) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "cron repository is not configured")
	}
	_, err := r.db.ExecContext(ctx, `
		UPDATE cron_task_runs
		SET status = ?, claimed_count = ?, processed_count = ?, success_count = ?, failure_count = ?,
		    finished_at_ms = ?, duration_ms = ?, error_message = ?
		WHERE run_id = ?
	`, run.Status, run.ClaimedCount, run.ProcessedCount, run.SuccessCount, run.FailureCount, run.FinishedAtMs, run.DurationMs, truncateError(run.ErrorMessage), run.RunID)
	return err
}

func truncateError(value string) string {
	value = strings.TrimSpace(value)
	if len(value) <= 512 {
		return value
	}
	return value[:512]
}
