package businessscope

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

type PublishResult struct {
	Changed       bool
	ActiveVersion string
}

func ValidateSnapshot(snapshot Snapshot, maxRejected int, maxRejectRatio float64) error {
	if snapshot.Candidates <= 0 {
		return fmt.Errorf("OneOS scope snapshot contains no candidate rows")
	}
	if len(snapshot.Items) <= 0 {
		return fmt.Errorf("OneOS scope snapshot contains no publishable rows")
	}
	if snapshot.Candidates != len(snapshot.Items)+len(snapshot.Rejections) {
		return fmt.Errorf("scope accounting mismatch: candidates=%d accepted=%d rejected=%d", snapshot.Candidates, len(snapshot.Items), len(snapshot.Rejections))
	}
	if maxRejected >= 0 && len(snapshot.Rejections) > maxRejected {
		return fmt.Errorf("scope rejected rows exceed limit: rejected=%d limit=%d", len(snapshot.Rejections), maxRejected)
	}
	ratio := float64(len(snapshot.Rejections)) / float64(snapshot.Candidates)
	if maxRejectRatio >= 0 && ratio > maxRejectRatio {
		return fmt.Errorf("scope rejected ratio exceeds limit: ratio=%.6f limit=%.6f", ratio, maxRejectRatio)
	}
	return nil
}

func Publish(ctx context.Context, db *sql.DB, snapshot Snapshot) (PublishResult, error) {
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
	if err != nil {
		return PublishResult{}, fmt.Errorf("begin scope publish: %w", err)
	}
	defer tx.Rollback()
	var activeVersion sql.NullString
	if err := tx.QueryRowContext(ctx, `SELECT active_version FROM business_scope_state WHERE id=1 FOR UPDATE`).Scan(&activeVersion); err != nil {
		return PublishResult{}, fmt.Errorf("lock business scope state; apply migration 012_business_scope_projection.sql: %w", err)
	}
	status := "published"
	changed := !activeVersion.Valid || activeVersion.String != snapshot.SourceVersion
	if !changed {
		status = "unchanged"
	}
	now := time.Now().UTC()
	if _, err := tx.ExecContext(ctx, `INSERT INTO business_scope_sync_run(
run_id,source_system,source_version,status,candidate_count,accepted_count,rejected_count,source_checksum,started_at,finished_at
) VALUES(?,?,?,?,?,?,?,?,?,?)`, snapshot.RunID, SourceSystem, snapshot.SourceVersion, status, snapshot.Candidates, len(snapshot.Items), len(snapshot.Rejections), snapshot.Checksum, snapshot.GeneratedAt, now); err != nil {
		return PublishResult{}, fmt.Errorf("insert business scope sync run: %w", err)
	}
	if changed {
		// A source version can become active again after the source data reverts.
		// Rebuild that inactive version while holding the state lock so publishing
		// remains atomic and cannot fail on its existing primary keys.
		if _, err := tx.ExecContext(ctx, `DELETE FROM business_customer_vehicle_scope WHERE source_version=?`, snapshot.SourceVersion); err != nil {
			return PublishResult{}, fmt.Errorf("clear inactive business scope version: %w", err)
		}
		statement, err := tx.PrepareContext(ctx, `INSERT INTO business_customer_vehicle_scope(
source_version,customer_id,vin,vehicle_id,contract_id,contract_code,plate_number,project_name,operation_status,scope_start_at,source_updated_at,published_at
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`)
		if err != nil {
			return PublishResult{}, fmt.Errorf("prepare business scope insert: %w", err)
		}
		defer statement.Close()
		for _, item := range snapshot.Items {
			var contractID any
			if item.ContractID > 0 {
				contractID = item.ContractID
			}
			if _, err := statement.ExecContext(ctx, snapshot.SourceVersion, item.CustomerID, item.VIN, item.VehicleID, contractID,
				item.ContractCode, item.PlateNumber, item.ProjectName, item.OperationStatus, item.ScopeStartAt, item.SourceUpdatedAt, now); err != nil {
				return PublishResult{}, fmt.Errorf("insert business scope item: %w", err)
			}
		}
	}
	if len(snapshot.Rejections) > 0 {
		statement, err := tx.PrepareContext(ctx, `INSERT INTO business_scope_rejection(
run_id,row_number,vehicle_id,vin,customer_id,contract_id,reason_code,created_at
) VALUES(?,?,?,?,?,?,?,?)`)
		if err != nil {
			return PublishResult{}, fmt.Errorf("prepare scope rejection insert: %w", err)
		}
		defer statement.Close()
		for _, rejected := range snapshot.Rejections {
			if _, err := statement.ExecContext(ctx, snapshot.RunID, rejected.RowNumber, rejected.VehicleID, rejected.VIN,
				rejected.CustomerID, rejected.ContractID, rejected.ReasonCode, now); err != nil {
				return PublishResult{}, fmt.Errorf("insert business scope rejection: %w", err)
			}
		}
	}
	if _, err := tx.ExecContext(ctx, `UPDATE business_scope_state SET
active_version=?,source_checksum=?,candidate_count=?,accepted_count=?,rejected_count=?,generated_at=?,published_at=?,last_success_at=?
WHERE id=1`, snapshot.SourceVersion, snapshot.Checksum, snapshot.Candidates, len(snapshot.Items), len(snapshot.Rejections), snapshot.GeneratedAt, now, now); err != nil {
		return PublishResult{}, fmt.Errorf("activate business scope snapshot: %w", err)
	}
	if err := tx.Commit(); err != nil {
		return PublishResult{}, fmt.Errorf("commit business scope snapshot: %w", err)
	}
	return PublishResult{Changed: changed, ActiveVersion: snapshot.SourceVersion}, nil
}
