package repository

import (
	"fmt"
	"strings"
	"time"

	"clawreef/internal/models"

	"github.com/upper/db/v4"
)

type SkillRepository interface {
	ListSkillsByUser(userID int) ([]models.Skill, error)
	ListAllSkills() ([]models.Skill, error)
	GetSkillByID(id int) (*models.Skill, error)
	GetSkillByUserKey(userID int, skillKey string) (*models.Skill, error)
	CreateSkill(skill *models.Skill) error
	UpdateSkill(skill *models.Skill) error
	DeleteSkill(id int) error
	GetBlobByContentHash(hash string) (*models.SkillBlob, error)
	GetBlobByID(id int) (*models.SkillBlob, error)
	CreateBlob(blob *models.SkillBlob) error
	UpdateBlob(blob *models.SkillBlob) error
	ListVersionsBySkillID(skillID int) ([]models.SkillVersion, error)
	GetVersionByID(id int) (*models.SkillVersion, error)
	GetVersionBySkillAndBlob(skillID, blobID int) (*models.SkillVersion, error)
	GetLatestVersionBySkillID(skillID int) (*models.SkillVersion, error)
	CreateVersion(version *models.SkillVersion) error
	ListInstanceSkills(instanceID int) ([]models.InstanceSkill, error)
	GetInstanceSkill(instanceID, skillID int) (*models.InstanceSkill, error)
	UpsertInstanceSkill(item *models.InstanceSkill) error
	MarkMissingInstanceSkills(instanceID int, activeSkillIDs []int, observedAt time.Time) error
	CreateScanResult(result *models.SkillScanResult) error
	GetScanResultByID(id int) (*models.SkillScanResult, error)
	ListScanResultsByBlobID(blobID int) ([]models.SkillScanResult, error)
	GetLatestScanResultByBlobID(blobID int) (*models.SkillScanResult, error)
	GetLatestScanResultBySkillID(skillID int) (*models.SkillScanResult, error)
}

type skillRepository struct{ sess db.Session }

func NewSkillRepository(sess db.Session) SkillRepository { return &skillRepository{sess: sess} }

func (r *skillRepository) ListSkillsByUser(userID int) ([]models.Skill, error) {
	var items []models.Skill
	if err := r.sess.Collection("skills").Find(db.Cond{"user_id": userID}).OrderBy("-updated_at", "-id").All(&items); err != nil {
		return nil, fmt.Errorf("failed to list skills by user: %w", err)
	}
	return items, nil
}

func (r *skillRepository) ListAllSkills() ([]models.Skill, error) {
	var items []models.Skill
	if err := r.sess.Collection("skills").Find().OrderBy("-updated_at", "-id").All(&items); err != nil {
		return nil, fmt.Errorf("failed to list all skills: %w", err)
	}
	return items, nil
}

func (r *skillRepository) GetSkillByID(id int) (*models.Skill, error) {
	var item models.Skill
	if err := r.sess.Collection("skills").Find(db.Cond{"id": id}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) GetSkillByUserKey(userID int, skillKey string) (*models.Skill, error) {
	var item models.Skill
	if err := r.sess.Collection("skills").Find(db.Cond{"user_id": userID, "skill_key": skillKey}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill by key: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) CreateSkill(skill *models.Skill) error {
	ensureTimestamps(&skill.CreatedAt, &skill.UpdatedAt)
	res, err := r.sess.Collection("skills").Insert(skill)
	if err != nil {
		return fmt.Errorf("failed to create skill: %w", err)
	}
	if id, ok := res.ID().(int64); ok {
		skill.ID = int(id)
	}
	return nil
}

func (r *skillRepository) UpdateSkill(skill *models.Skill) error {
	if skill.UpdatedAt.IsZero() {
		skill.UpdatedAt = time.Now().UTC()
	}
	if err := r.sess.Collection("skills").Find(db.Cond{"id": skill.ID}).Update(skill); err != nil {
		return fmt.Errorf("failed to update skill: %w", err)
	}
	return nil
}

func (r *skillRepository) DeleteSkill(id int) error {
	if err := r.sess.Collection("skills").Find(db.Cond{"id": id}).Delete(); err != nil {
		return fmt.Errorf("failed to delete skill: %w", err)
	}
	return nil
}

func (r *skillRepository) GetBlobByContentHash(hash string) (*models.SkillBlob, error) {
	var item models.SkillBlob
	if err := r.sess.Collection("skill_blobs").Find(db.Cond{"content_hash": hash}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill blob by content hash: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) GetBlobByID(id int) (*models.SkillBlob, error) {
	var item models.SkillBlob
	if err := r.sess.Collection("skill_blobs").Find(db.Cond{"id": id}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill blob: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) CreateBlob(blob *models.SkillBlob) error {
	ensureTimestamps(&blob.CreatedAt, &blob.UpdatedAt)
	res, err := r.sess.Collection("skill_blobs").Insert(blob)
	if err != nil {
		return fmt.Errorf("failed to create skill blob: %w", err)
	}
	if id, ok := res.ID().(int64); ok {
		blob.ID = int(id)
	}
	return nil
}

func (r *skillRepository) UpdateBlob(blob *models.SkillBlob) error {
	if blob.UpdatedAt.IsZero() {
		blob.UpdatedAt = time.Now().UTC()
	}
	if err := r.sess.Collection("skill_blobs").Find(db.Cond{"id": blob.ID}).Update(blob); err != nil {
		return fmt.Errorf("failed to update skill blob: %w", err)
	}
	return nil
}

func (r *skillRepository) ListVersionsBySkillID(skillID int) ([]models.SkillVersion, error) {
	var items []models.SkillVersion
	if err := r.sess.Collection("skill_versions").Find(db.Cond{"skill_id": skillID}).OrderBy("-version_no", "-id").All(&items); err != nil {
		return nil, fmt.Errorf("failed to list skill versions: %w", err)
	}
	return items, nil
}

func (r *skillRepository) GetVersionByID(id int) (*models.SkillVersion, error) {
	var item models.SkillVersion
	if err := r.sess.Collection("skill_versions").Find(db.Cond{"id": id}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill version: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) GetVersionBySkillAndBlob(skillID, blobID int) (*models.SkillVersion, error) {
	var item models.SkillVersion
	if err := r.sess.Collection("skill_versions").Find(db.Cond{"skill_id": skillID, "blob_id": blobID}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill version by blob: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) GetLatestVersionBySkillID(skillID int) (*models.SkillVersion, error) {
	var item models.SkillVersion
	if err := r.sess.Collection("skill_versions").Find(db.Cond{"skill_id": skillID}).OrderBy("-version_no", "-id").One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get latest skill version: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) CreateVersion(version *models.SkillVersion) error {
	ensureTimestamps(&version.CreatedAt, &version.UpdatedAt)
	res, err := r.sess.Collection("skill_versions").Insert(version)
	if err != nil {
		return fmt.Errorf("failed to create skill version: %w", err)
	}
	if id, ok := res.ID().(int64); ok {
		version.ID = int(id)
	}
	return nil
}

func (r *skillRepository) ListInstanceSkills(instanceID int) ([]models.InstanceSkill, error) {
	var items []models.InstanceSkill
	if err := r.sess.Collection("instance_skills").Find(db.Cond{"instance_id": instanceID}).OrderBy("-updated_at", "-id").All(&items); err != nil {
		return nil, fmt.Errorf("failed to list instance skills: %w", err)
	}
	return items, nil
}

func (r *skillRepository) GetInstanceSkill(instanceID, skillID int) (*models.InstanceSkill, error) {
	var item models.InstanceSkill
	if err := r.sess.Collection("instance_skills").Find(db.Cond{"instance_id": instanceID, "skill_id": skillID}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get instance skill: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) UpsertInstanceSkill(item *models.InstanceSkill) error {
	existing, err := r.GetInstanceSkill(item.InstanceID, item.SkillID)
	if err != nil {
		return err
	}
	if existing == nil {
		ensureTimestamps(&item.CreatedAt, &item.UpdatedAt)
		res, err := r.sess.Collection("instance_skills").Insert(item)
		if err != nil {
			if !isDuplicateEntryError(err) {
				return fmt.Errorf("failed to create instance skill: %w", err)
			}
			existing, err = r.GetInstanceSkill(item.InstanceID, item.SkillID)
			if err != nil {
				return err
			}
			if existing == nil {
				return fmt.Errorf("failed to create instance skill: %w", err)
			}
			item.ID = existing.ID
			item.CreatedAt = existing.CreatedAt
			if item.UpdatedAt.IsZero() {
				item.UpdatedAt = time.Now().UTC()
			}
			if err := r.sess.Collection("instance_skills").Find(db.Cond{"id": existing.ID}).Update(item); err != nil {
				return fmt.Errorf("failed to update instance skill after duplicate insert: %w", err)
			}
			return nil
		}
		if id, ok := res.ID().(int64); ok {
			item.ID = int(id)
		}
		return nil
	}
	item.ID = existing.ID
	item.CreatedAt = existing.CreatedAt
	if item.UpdatedAt.IsZero() {
		item.UpdatedAt = time.Now().UTC()
	}
	if err := r.sess.Collection("instance_skills").Find(db.Cond{"id": existing.ID}).Update(item); err != nil {
		return fmt.Errorf("failed to update instance skill: %w", err)
	}
	return nil
}

func isDuplicateEntryError(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(strings.ToLower(err.Error()), "duplicate entry")
}

func (r *skillRepository) MarkMissingInstanceSkills(instanceID int, activeSkillIDs []int, observedAt time.Time) error {
	find := r.sess.Collection("instance_skills").Find(db.Cond{"instance_id": instanceID})
	if len(activeSkillIDs) > 0 {
		find = find.And(db.Cond{"skill_id NOT IN": activeSkillIDs})
	}
	var items []models.InstanceSkill
	if err := find.All(&items); err != nil && err != db.ErrNoMoreRows {
		return fmt.Errorf("failed to list stale instance skills: %w", err)
	}
	for _, item := range items {
		item.Status = "removed"
		item.RemovedAt = &observedAt
		item.UpdatedAt = observedAt
		if err := r.sess.Collection("instance_skills").Find(db.Cond{"id": item.ID}).Update(item); err != nil {
			return fmt.Errorf("failed to mark instance skill removed: %w", err)
		}
	}
	return nil
}

func (r *skillRepository) CreateScanResult(result *models.SkillScanResult) error {
	ensureTimestamps(&result.CreatedAt, &result.UpdatedAt)
	res, err := r.sess.Collection("skill_scan_results").Insert(result)
	if err != nil {
		return fmt.Errorf("failed to create skill scan result: %w", err)
	}
	if id, ok := res.ID().(int64); ok {
		result.ID = int(id)
	}
	return nil
}

func (r *skillRepository) GetScanResultByID(id int) (*models.SkillScanResult, error) {
	var item models.SkillScanResult
	if err := r.sess.Collection("skill_scan_results").Find(db.Cond{"id": id}).One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get skill scan result: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) ListScanResultsByBlobID(blobID int) ([]models.SkillScanResult, error) {
	var items []models.SkillScanResult
	if err := r.sess.Collection("skill_scan_results").Find(db.Cond{"blob_id": blobID}).OrderBy("-scanned_at", "-id").All(&items); err != nil {
		return nil, fmt.Errorf("failed to list skill scan results: %w", err)
	}
	return items, nil
}

func (r *skillRepository) GetLatestScanResultByBlobID(blobID int) (*models.SkillScanResult, error) {
	var item models.SkillScanResult
	if err := r.sess.Collection("skill_scan_results").Find(db.Cond{"blob_id": blobID}).OrderBy("-scanned_at", "-id").One(&item); err != nil {
		if err == db.ErrNoMoreRows {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get latest skill scan result: %w", err)
	}
	return &item, nil
}

func (r *skillRepository) GetLatestScanResultBySkillID(skillID int) (*models.SkillScanResult, error) {
	skill, err := r.GetSkillByID(skillID)
	if err != nil || skill == nil || skill.LastScanResultID == nil {
		return nil, err
	}
	return r.GetScanResultByID(*skill.LastScanResultID)
}
