// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later

package issues

import (
	"context"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)

func init() {
	db.RegisterModel(new(IssueStatusDef))
}

// IssueStatusDef defines a custom issue status at the org level.
type IssueStatusDef struct {
	ID          int64              `xorm:"pk autoincr"`
	OrgID       int64              `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
	Name        string             `xorm:"NOT NULL"`
	Color       string             `xorm:"VARCHAR(7)"`  // hex color, e.g. "#e11d48"
	Description string             `xorm:"TEXT"`
	ClosesIssue bool               `xorm:"NOT NULL DEFAULT false 'closes_issue'"`
	IsRequired  bool               `xorm:"NOT NULL DEFAULT false 'is_required'"` // cannot be deleted
	SortOrder   int                `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
	IsActive    bool               `xorm:"NOT NULL DEFAULT true 'is_active'"`
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
	UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}

func (IssueStatusDef) TableName() string {
	return "issue_status_def"
}

// ──────────────────────────────────────────────────────────────────────
// Queries
// ──────────────────────────────────────────────────────────────────────

// GetIssueStatusDefsByOrg returns active status definitions for an org.
// If none exist, seeds the org with default statuses automatically.
func GetIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDef, error) {
	defs := make([]*IssueStatusDef, 0, 10)
	if err := db.GetEngine(ctx).
		Where("org_id = ? AND is_active = ?", orgID, true).
		OrderBy("sort_order ASC, id ASC").
		Find(&defs); err != nil {
		return nil, err
	}
	if len(defs) == 0 && orgID > 0 {
		if err := seedDefaultIssueStatuses(ctx, orgID); err != nil {
			return defs, nil // non-fatal
		}
		return GetIssueStatusDefsByOrg(ctx, orgID)
	}
	return defs, nil
}

// seedDefaultIssueStatuses creates the standard status presets for an org.
// Open and Closed are required (is_required=true) and cannot be deleted.
func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error {
	defaults := []*IssueStatusDef{
		{OrgID: orgID, Name: "Open", Color: "#2563eb", Description: "New or active issue", ClosesIssue: false, IsRequired: true, SortOrder: 0, IsActive: true},
		{OrgID: orgID, Name: "In Progress", Color: "#7c3aed", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
		{OrgID: orgID, Name: "Waiting", Color: "#f59e0b", Description: "Blocked or waiting for input", SortOrder: 2, IsActive: true},
		{OrgID: orgID, Name: "In Review", Color: "#0891b2", Description: "PR submitted, awaiting review", SortOrder: 3, IsActive: true},
		{OrgID: orgID, Name: "Closed", Color: "#16a34a", Description: "Completed or resolved", ClosesIssue: true, IsRequired: true, SortOrder: 4, IsActive: true},
		{OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true},
	}
	for _, d := range defaults {
		if _, err := db.GetEngine(ctx).Insert(d); err != nil {
			return err
		}
	}
	return nil
}

// GetAllIssueStatusDefsByOrg returns all status definitions (including inactive).
func GetAllIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDef, error) {
	defs := make([]*IssueStatusDef, 0, 10)
	return defs, db.GetEngine(ctx).
		Where("org_id = ?", orgID).
		OrderBy("sort_order ASC, id ASC").
		Find(&defs)
}

// GetIssueStatusDefByID returns a single status definition.
func GetIssueStatusDefByID(ctx context.Context, id int64) (*IssueStatusDef, error) {
	def := new(IssueStatusDef)
	has, err := db.GetEngine(ctx).ID(id).Get(def)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, db.ErrNotExist{Resource: "IssueStatusDef", ID: id}
	}
	return def, nil
}

// ──────────────────────────────────────────────────────────────────────
// CRUD
// ──────────────────────────────────────────────────────────────────────

// CreateIssueStatusDef creates a new status definition.
func CreateIssueStatusDef(ctx context.Context, def *IssueStatusDef) error {
	_, err := db.GetEngine(ctx).Insert(def)
	return err
}

// UpdateIssueStatusDef updates a status definition.
func UpdateIssueStatusDef(ctx context.Context, def *IssueStatusDef) error {
	_, err := db.GetEngine(ctx).ID(def.ID).AllCols().Update(def)
	return err
}

// ErrStatusRequired is returned when trying to delete a required status.
type ErrStatusRequired struct {
	ID   int64
	Name string
}

func (e ErrStatusRequired) Error() string {
	return "status is required and cannot be deleted"
}

// IsErrStatusRequired checks if an error is ErrStatusRequired.
func IsErrStatusRequired(err error) bool {
	_, ok := err.(ErrStatusRequired)
	return ok
}

// DeleteIssueStatusDef deletes a status definition and clears references on issues.
// Returns ErrStatusRequired if the status is marked as required.
func DeleteIssueStatusDef(ctx context.Context, id int64) error {
	def, err := GetIssueStatusDefByID(ctx, id)
	if err != nil {
		return err
	}
	if def.IsRequired {
		return ErrStatusRequired{ID: def.ID, Name: def.Name}
	}
	// Clear status_id on all issues that reference this definition
	if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = 0 WHERE status_id = ?", id); err != nil {
		return err
	}
	_, err = db.GetEngine(ctx).ID(id).Delete(new(IssueStatusDef))
	return err
}

// ──────────────────────────────────────────────────────────────────────
// Issue status helpers
// ──────────────────────────────────────────────────────────────────────

// SetIssueStatusID updates the status_id on an issue.
func SetIssueStatusID(ctx context.Context, issueID, statusID int64) error {
	_, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = ? WHERE id = ?", statusID, issueID)
	return err
}
