package storage

import (
	"context"
	"errors"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/nomadcode/nomadcode-core/internal/db"
)

// pgUniqueViolation is the SQLSTATE code Postgres returns for a unique
// constraint violation.
const pgUniqueViolation = "23505"

// ErrRoadmapSyncIdentityNotFound is returned when no identity row matches the
// lookup key.
var ErrRoadmapSyncIdentityNotFound = errors.New("roadmap sync identity not found")

// ErrRoadmapSyncIdentityConflict is returned when the incoming provider work
// item and Milestone path do not agree with an existing identity row: either
// the Milestone path is already bound to a different provider work item, or the
// provider work item is already bound to a different Milestone path. Returning a
// typed conflict keeps a re-process from silently rebinding one identity onto
// another instead of hitting a raw unique-index violation.
var ErrRoadmapSyncIdentityConflict = errors.New("roadmap sync identity conflict")

// GetRoadmapSyncIdentityByWorkItem returns the identity bound to a provider work
// item, or ErrRoadmapSyncIdentityNotFound when none exists.
func (s *Store) GetRoadmapSyncIdentityByWorkItem(ctx context.Context, args db.GetRoadmapSyncIdentityByWorkItemParams) (db.RoadmapSyncIdentity, error) {
	row, err := s.queries.GetRoadmapSyncIdentityByWorkItem(ctx, args)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return db.RoadmapSyncIdentity{}, ErrRoadmapSyncIdentityNotFound
		}
		return db.RoadmapSyncIdentity{}, err
	}
	return row, nil
}

// GetRoadmapSyncIdentityByMilestonePath returns the identity bound to a roadmap
// Milestone path, or ErrRoadmapSyncIdentityNotFound when none exists.
func (s *Store) GetRoadmapSyncIdentityByMilestonePath(ctx context.Context, milestonePath string) (db.RoadmapSyncIdentity, error) {
	row, err := s.queries.GetRoadmapSyncIdentityByMilestonePath(ctx, milestonePath)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return db.RoadmapSyncIdentity{}, ErrRoadmapSyncIdentityNotFound
		}
		return db.RoadmapSyncIdentity{}, err
	}
	return row, nil
}

// UpsertRoadmapSyncIdentity persists an identity keyed on the provider work
// item, reusing the existing row when the same Plane ticket or the same
// Milestone path is re-processed.
//
// Conflict protection is atomic in a single statement rather than a non-atomic
// read-then-write: the upsert's DO UPDATE keeps the stored roadmap_milestone_path
// and only fires when it already equals the incoming path. So a re-process of
// the same provider work item with a different Milestone path matches no row on
// conflict and returns pgx.ErrNoRows, which maps to ErrRoadmapSyncIdentityConflict
// instead of silently rebinding the ticket to a new path. A different work item
// re-using an existing Milestone path trips the path unique index (SQLSTATE
// 23505), which also maps to ErrRoadmapSyncIdentityConflict instead of a raw pg
// error. Two concurrent re-processes therefore cannot both win.
func (s *Store) UpsertRoadmapSyncIdentity(ctx context.Context, args db.UpsertRoadmapSyncIdentityByWorkItemParams) (db.RoadmapSyncIdentity, error) {
	row, err := s.queries.UpsertRoadmapSyncIdentityByWorkItem(ctx, args)
	if err != nil {
		// Work item already bound to a different Milestone path: the conditional
		// DO UPDATE matched no row, so RETURNING produced nothing.
		if errors.Is(err, pgx.ErrNoRows) {
			return db.RoadmapSyncIdentity{}, ErrRoadmapSyncIdentityConflict
		}
		// Different work item re-using an existing Milestone path: the path unique
		// index rejected the INSERT.
		var pgErr *pgconn.PgError
		if errors.As(err, &pgErr) && pgErr.Code == pgUniqueViolation {
			return db.RoadmapSyncIdentity{}, ErrRoadmapSyncIdentityConflict
		}
		return db.RoadmapSyncIdentity{}, err
	}
	return row, nil
}
