package projectsync

import (
	"errors"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/nomadcode/nomadcode-core/internal/db"
	"github.com/nomadcode/nomadcode-core/internal/workitem"
)

var ErrInvalidConfig = errors.New("invalid project sync config")

const (
	DefaultSlotIndex SlotIndex = 0
	MaxSlotIndex     SlotIndex = 999
)

type BranchName string

const (
	BranchNameMain    BranchName = "main"
	BranchNameDevelop BranchName = "develop"
)

// reservedBranchNames is the closed set of branch workspace names. Only these
// branches get a fixed directory under branches/; agent slots use slots/NNN.
var reservedBranchNames = map[BranchName]struct{}{
	BranchNameMain:    {},
	BranchNameDevelop: {},
}

func (b BranchName) Valid() bool {
	_, ok := reservedBranchNames[b]
	return ok
}

type BranchSyncState string

const (
	BranchSyncStateSynced BranchSyncState = "synced"
	BranchSyncStateStale  BranchSyncState = "stale"
	BranchSyncStateError  BranchSyncState = "error"
)

func (s BranchSyncState) Valid() bool {
	switch s {
	case BranchSyncStateSynced, BranchSyncStateStale, BranchSyncStateError:
		return true
	default:
		return false
	}
}

// RequiredBranchWorkspaces returns the fixed branch names every project
// workspace must provision under branches/.
func RequiredBranchWorkspaces() []BranchName {
	return []BranchName{BranchNameMain, BranchNameDevelop}
}

// BranchWorkspacePath returns the filesystem path for a named branch workspace
// under the project workspace root. Only reserved branch names are accepted;
// agent slots must use SlotWorkspacePath instead.
func BranchWorkspacePath(workspaceBasePath, repoDirName string, branch BranchName) (string, error) {
	if !branch.Valid() {
		return "", ErrInvalidConfig
	}
	root, err := ProjectWorkspaceRoot(workspaceBasePath, repoDirName)
	if err != nil {
		return "", err
	}
	return filepath.Join(root, "branches", string(branch)), nil
}

type ProviderProjectTarget struct {
	Provider workitem.ProviderID
	Tenant   string
	Project  string
}

type Config struct {
	Target            ProviderProjectTarget
	GitRemoteURL      string
	SourceBranch      string
	WorkspaceID       string
	WorkspaceBasePath string
	RepoDirName       string
}

type SlotIndex int

type SlotState string

const (
	SlotStateAvailable SlotState = "available"
	SlotStateInUse     SlotState = "in_use"
	SlotStateDirty     SlotState = "dirty"
	SlotStateError     SlotState = "error"
)

func (t ProviderProjectTarget) Normalize() (ProviderProjectTarget, error) {
	target := ProviderProjectTarget{
		Provider: workitem.ProviderID(strings.TrimSpace(string(t.Provider))),
		Tenant:   strings.TrimSpace(t.Tenant),
		Project:  strings.TrimSpace(t.Project),
	}
	if target.Provider == "" || target.Tenant == "" || target.Project == "" {
		return ProviderProjectTarget{}, ErrInvalidConfig
	}
	return target, nil
}

func (c Config) Normalize() (Config, error) {
	target, err := c.Target.Normalize()
	if err != nil {
		return Config{}, err
	}

	basePath, err := normalizePath(c.WorkspaceBasePath)
	if err != nil {
		return Config{}, err
	}

	repoDir, err := normalizeRepoDirName(c.RepoDirName)
	if err != nil {
		return Config{}, err
	}

	config := Config{
		Target:            target,
		GitRemoteURL:      strings.TrimSpace(c.GitRemoteURL),
		SourceBranch:      strings.TrimSpace(c.SourceBranch),
		WorkspaceID:       strings.TrimSpace(c.WorkspaceID),
		WorkspaceBasePath: basePath,
		RepoDirName:       repoDir,
	}
	if config.GitRemoteURL == "" || config.SourceBranch == "" || config.WorkspaceID == "" {
		return Config{}, ErrInvalidConfig
	}
	return config, nil
}

func (c Config) ProjectWorkspaceRoot() (string, error) {
	config, err := c.Normalize()
	if err != nil {
		return "", err
	}
	return ProjectWorkspaceRoot(config.WorkspaceBasePath, config.RepoDirName)
}

func (c Config) SlotWorkspacePath(slot SlotIndex) (string, error) {
	config, err := c.Normalize()
	if err != nil {
		return "", err
	}
	return SlotWorkspacePath(config.WorkspaceBasePath, config.RepoDirName, slot)
}

func NewSlotIndex(index int) (SlotIndex, error) {
	slot := SlotIndex(index)
	if !slot.Valid() {
		return 0, ErrInvalidConfig
	}
	return slot, nil
}

func (s SlotIndex) Valid() bool {
	return s >= DefaultSlotIndex && s <= MaxSlotIndex
}

func (s SlotIndex) String() string {
	return fmt.Sprintf("%03d", int(s))
}

func (s SlotIndex) PathName() (string, error) {
	if !s.Valid() {
		return "", ErrInvalidConfig
	}
	return s.String(), nil
}

func (s SlotState) Valid() bool {
	switch s {
	case SlotStateAvailable, SlotStateInUse, SlotStateDirty, SlotStateError:
		return true
	default:
		return false
	}
}

func (s SlotState) Allocatable() bool {
	return s == SlotStateAvailable
}

func ProjectWorkspaceRoot(workspaceBasePath, repoDirName string) (string, error) {
	basePath, err := normalizePath(workspaceBasePath)
	if err != nil {
		return "", err
	}
	repoDir, err := normalizeRepoDirName(repoDirName)
	if err != nil {
		return "", err
	}
	return filepath.Join(basePath, repoDir), nil
}

func SlotWorkspacePath(workspaceBasePath, repoDirName string, slot SlotIndex) (string, error) {
	slotName, err := slot.PathName()
	if err != nil {
		return "", err
	}
	root, err := ProjectWorkspaceRoot(workspaceBasePath, repoDirName)
	if err != nil {
		return "", err
	}
	return filepath.Join(root, "slots", slotName), nil
}

func normalizePath(value string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "", ErrInvalidConfig
	}
	cleaned := filepath.Clean(trimmed)
	if cleaned == "." {
		return "", ErrInvalidConfig
	}
	return cleaned, nil
}

func normalizeRepoDirName(value string) (string, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return "", ErrInvalidConfig
	}
	cleaned := filepath.Clean(trimmed)
	if cleaned == "." || cleaned == ".." || filepath.IsAbs(cleaned) || strings.ContainsAny(cleaned, `/\`) {
		return "", ErrInvalidConfig
	}
	return cleaned, nil
}
func (c Config) ToCreateProjectSyncParams() db.CreateProjectSyncSettingParams {
	return db.CreateProjectSyncSettingParams{
		Provider:          string(c.Target.Provider),
		Tenant:            c.Target.Tenant,
		Project:           c.Target.Project,
		GitRemoteUrl:      c.GitRemoteURL,
		SourceBranch:      c.SourceBranch,
		WorkspaceID:       c.WorkspaceID,
		WorkspaceBasePath: c.WorkspaceBasePath,
		RepoDirName:       c.RepoDirName,
	}
}

// WorkspaceSlot is the typed view of a persisted workspace slot row, keeping
// the slot index and state as projectsync domain types.
type WorkspaceSlot struct {
	ID                   int64
	ProjectSyncSettingID int64
	Index                SlotIndex
	State                SlotState
	Path                 string
}

// SlotFromDBRecord converts a generated db row into the typed WorkspaceSlot.
func SlotFromDBRecord(r db.WorkspaceSlot) WorkspaceSlot {
	return WorkspaceSlot{
		ID:                   r.ID,
		ProjectSyncSettingID: r.ProjectSyncSettingID,
		Index:                SlotIndex(r.SlotIndex),
		State:                SlotState(r.State),
		Path:                 r.Path,
	}
}

// ToUpsertWorkspaceSlotParams builds DB upsert params for a slot, rejecting an
// out-of-range index before it reaches the database.
func ToUpsertWorkspaceSlotParams(projectSyncSettingID int64, index SlotIndex, path string) (db.UpsertWorkspaceSlotParams, error) {
	if !index.Valid() {
		return db.UpsertWorkspaceSlotParams{}, ErrInvalidConfig
	}
	return db.UpsertWorkspaceSlotParams{
		ProjectSyncSettingID: projectSyncSettingID,
		SlotIndex:            int32(index),
		Path:                 path,
	}, nil
}

// ToUpdateWorkspaceSlotStateParams builds DB params for a slot state
// transition, rejecting unknown states to mirror the DB check constraint.
func ToUpdateWorkspaceSlotStateParams(id int64, state SlotState) (db.UpdateWorkspaceSlotStateParams, error) {
	if !state.Valid() {
		return db.UpdateWorkspaceSlotStateParams{}, ErrInvalidConfig
	}
	return db.UpdateWorkspaceSlotStateParams{
		ID:    id,
		State: string(state),
	}, nil
}

func ConfigFromDBRecord(r db.ProjectSyncSetting) Config {
	return Config{
		Target: ProviderProjectTarget{
			Provider: workitem.ProviderID(r.Provider),
			Tenant:   r.Tenant,
			Project:  r.Project,
		},
		GitRemoteURL:      r.GitRemoteUrl,
		SourceBranch:      r.SourceBranch,
		WorkspaceID:       r.WorkspaceID,
		WorkspaceBasePath: r.WorkspaceBasePath,
		RepoDirName:       r.RepoDirName,
	}
}
