package storage

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/jackc/pgx/v5/pgconn"

	"git.toki-labs.com/toki/gito/services/core/internal/core"
)

func TestPgRepoStoreValidatesRequiredFieldsBeforeDatabase(t *testing.T) {
	store := &pgRepoStore{}
	err := store.CreateRepo(context.Background(), core.Repo{
		ID:            "repo-missing-remote",
		Name:          "Missing Remote",
		DefaultBranch: "main",
		WorkspaceRoot: "/workspaces/missing-remote",
	})
	if !errors.Is(err, ErrInvalidRepo) {
		t.Fatalf("CreateRepo missing remote validation: got %v want %v", err, ErrInvalidRepo)
	}

	err = store.UpdateRepo(context.Background(), core.Repo{
		ID:            "repo-missing-name",
		RemoteURL:     "ssh://git.example.invalid/toki/repo.git",
		DefaultBranch: "main",
		WorkspaceRoot: "/workspaces/repo",
	})
	if !errors.Is(err, ErrInvalidRepo) {
		t.Fatalf("UpdateRepo missing name validation: got %v want %v", err, ErrInvalidRepo)
	}
}

func TestPgWorkspaceLeaseStoreValidatesBeforeDatabase(t *testing.T) {
	store := &pgWorkspaceLeaseStore{}
	err := store.SaveLease(context.Background(), core.WorkspaceLease{
		ID:     "lease-missing-state",
		RepoID: "repo",
		Slot:   "001",
		Path:   "/workspaces/repo/001",
	})
	if !errors.Is(err, ErrInvalidLease) {
		t.Fatalf("SaveLease missing state validation: got %v want %v", err, ErrInvalidLease)
	}

	_, _, err = store.AcquireAvailableLease(context.Background(), "", time.Now(), time.Hour)
	if !errors.Is(err, ErrInvalidLease) {
		t.Fatalf("AcquireAvailableLease missing repo validation: got %v want %v", err, ErrInvalidLease)
	}
}

func TestPgOperationStoreValidatesBeforeDatabase(t *testing.T) {
	store := &pgOperationStore{}
	base := core.Operation{
		ID:        "op-1",
		RepoID:    "repo-1",
		Type:      core.OperationClone,
		State:     core.OperationQueued,
		CreatedBy: "agent-shell",
	}

	tests := []struct {
		name   string
		mutate func(op core.Operation) core.Operation
	}{
		{
			name:   "missing id",
			mutate: func(op core.Operation) core.Operation { op.ID = "  "; return op },
		},
		{
			name:   "missing repo_id",
			mutate: func(op core.Operation) core.Operation { op.RepoID = ""; return op },
		},
		{
			name:   "missing created_by",
			mutate: func(op core.Operation) core.Operation { op.CreatedBy = ""; return op },
		},
		{
			name:   "invalid type",
			mutate: func(op core.Operation) core.Operation { op.Type = core.OperationType("merge"); return op },
		},
		{
			name:   "non-queued create state",
			mutate: func(op core.Operation) core.Operation { op.State = core.OperationRunning; return op },
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := store.CreateOperation(context.Background(), tt.mutate(base))
			if !errors.Is(err, ErrInvalidOperation) {
				t.Fatalf("CreateOperation %s: got %v want %v", tt.name, err, ErrInvalidOperation)
			}
		})
	}

	if _, err := store.GetOperation(context.Background(), "  "); !errors.Is(err, ErrInvalidOperation) {
		t.Fatalf("GetOperation empty id: got %v want %v", err, ErrInvalidOperation)
	}
	if _, err := store.ListOperations(context.Background(), ""); !errors.Is(err, ErrInvalidOperation) {
		t.Fatalf("ListOperations empty repo: got %v want %v", err, ErrInvalidOperation)
	}
	if _, err := store.StartOperation(context.Background(), "", time.Now()); !errors.Is(err, ErrInvalidOperation) {
		t.Fatalf("StartOperation empty id: got %v want %v", err, ErrInvalidOperation)
	}
}

func TestUniqueViolationConstraintRouting(t *testing.T) {
	tests := []struct {
		name           string
		err            error
		wantUnique     bool
		wantConstraint string
	}{
		{
			name:           "idempotency index conflict",
			err:            &pgconn.PgError{Code: "23505", ConstraintName: opIdempotencyConstraint},
			wantUnique:     true,
			wantConstraint: opIdempotencyConstraint,
		},
		{
			name:           "primary key collision",
			err:            &pgconn.PgError{Code: "23505", ConstraintName: "operations_pkey"},
			wantUnique:     true,
			wantConstraint: "operations_pkey",
		},
		{
			name:       "non-unique pg error",
			err:        &pgconn.PgError{Code: "23503", ConstraintName: "operations_repo_id_fkey"},
			wantUnique: false,
		},
		{
			name:       "non-pg error",
			err:        errors.New("connection reset"),
			wantUnique: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			constraint, unique := uniqueViolationConstraint(tt.err)
			if unique != tt.wantUnique {
				t.Fatalf("uniqueViolationConstraint unique: got %v want %v", unique, tt.wantUnique)
			}
			if unique && constraint != tt.wantConstraint {
				t.Fatalf("uniqueViolationConstraint name: got %q want %q", constraint, tt.wantConstraint)
			}
			if isUniqueViolation(tt.err) != tt.wantUnique {
				t.Fatalf("isUniqueViolation: got %v want %v", isUniqueViolation(tt.err), tt.wantUnique)
			}
		})
	}
}

func TestLeaseStateForReleaseDerivesStateFromOperationResult(t *testing.T) {
	tests := []struct {
		name    string
		release LeaseRelease
		want    core.WorkspaceState
		wantErr bool
	}{
		{
			name: "successful clean operation releases slot",
			release: LeaseRelease{
				OperationState: core.OperationSucceeded,
			},
			want: core.WorkspaceAvailable,
		},
		{
			name: "cancelled clean operation releases slot",
			release: LeaseRelease{
				OperationState: core.OperationCancelled,
			},
			want: core.WorkspaceAvailable,
		},
		{
			name: "failed clean operation marks error",
			release: LeaseRelease{
				OperationState: core.OperationFailed,
			},
			want: core.WorkspaceError,
		},
		{
			name: "dirty result wins over operation success",
			release: LeaseRelease{
				OperationState: core.OperationSucceeded,
				Dirty:          true,
			},
			want: core.WorkspaceDirty,
		},
		{
			name: "running operation cannot release slot",
			release: LeaseRelease{
				OperationState: core.OperationRunning,
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := leaseStateForRelease(tt.release)
			if tt.wantErr {
				if !errors.Is(err, ErrInvalidLease) {
					t.Fatalf("leaseStateForRelease error: got %v want %v", err, ErrInvalidLease)
				}
				return
			}
			if err != nil {
				t.Fatalf("leaseStateForRelease error: %v", err)
			}
			if got != tt.want {
				t.Fatalf("leaseStateForRelease state: got %q want %q", got, tt.want)
			}
		})
	}
}
