package repository

import (
	"context"
	"strings"

	"security-center-backend/internal/modules/department/model"

	"gorm.io/gorm"
)

type DepartmentRepository interface {
	Upsert(ctx context.Context, dept *model.Department) error
	List(ctx context.Context) ([]model.Department, error)
	FindByID(ctx context.Context, id uint64) (*model.Department, error)
	FindByLarkID(ctx context.Context, larkID string) (*model.Department, error)
	ListByLarkIDs(ctx context.Context, ids []string) ([]model.Department, error)
}

type EmployeeRepository interface {
	Upsert(ctx context.Context, emp *model.Employee) error
	List(ctx context.Context) ([]model.Employee, error)
	FindByID(ctx context.Context, id uint64) (*model.Employee, error)
	ListByDepartment(ctx context.Context, departmentID string) ([]model.Employee, error)
	FindByEmail(ctx context.Context, email string) (*model.Employee, error)
	FindByEmails(ctx context.Context, emails []string) ([]model.Employee, error)
	Search(ctx context.Context, query string) ([]model.Employee, error)
}

type OrgSyncRunRepository interface {
	Create(ctx context.Context, run *model.OrgSyncRun) error
	Update(ctx context.Context, run *model.OrgSyncRun) error
	ListRecent(ctx context.Context, limit int) ([]model.OrgSyncRun, error)
	LatestSuccessful(ctx context.Context) (*model.OrgSyncRun, error)
}

type GormDepartmentRepository struct {
	db *gorm.DB
}

func NewDepartmentRepository(db *gorm.DB) DepartmentRepository {
	return &GormDepartmentRepository{db: db}
}

func (r *GormDepartmentRepository) Upsert(ctx context.Context, dept *model.Department) error {
	// Use LarkDepartmentID as the unique key for upsert
	var existing model.Department
	err := r.db.WithContext(ctx).Where("lark_department_id = ?", dept.LarkDepartmentID).First(&existing).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return r.db.WithContext(ctx).Create(dept).Error
		}
		return err
	}
	// Update existing record, preserving timestamps
	return r.db.WithContext(ctx).Model(&existing).Updates(map[string]interface{}{
		"name":      dept.Name,
		"parent_id": dept.ParentID,
		"leader_id": dept.LeaderID,
		"status":    dept.Status,
	}).Error
}

func (r *GormDepartmentRepository) List(ctx context.Context) ([]model.Department, error) {
	var depts []model.Department
	err := r.db.WithContext(ctx).Find(&depts).Error
	return depts, err
}

func (r *GormDepartmentRepository) FindByID(ctx context.Context, id uint64) (*model.Department, error) {
	var dept model.Department
	err := r.db.WithContext(ctx).First(&dept, id).Error
	if err != nil {
		return nil, err
	}
	return &dept, nil
}

func (r *GormDepartmentRepository) FindByLarkID(ctx context.Context, larkID string) (*model.Department, error) {
	var dept model.Department
	err := r.db.WithContext(ctx).Where("lark_department_id = ?", larkID).First(&dept).Error
	if err != nil {
		return nil, err
	}
	return &dept, nil
}

func (r *GormDepartmentRepository) ListByLarkIDs(ctx context.Context, ids []string) ([]model.Department, error) {
	var depts []model.Department
	if len(ids) == 0 {
		return depts, nil
	}
	err := r.db.WithContext(ctx).Where("lark_department_id IN ?", ids).Find(&depts).Error
	return depts, err
}

type GormEmployeeRepository struct {
	db *gorm.DB
}

func NewEmployeeRepository(db *gorm.DB) EmployeeRepository {
	return &GormEmployeeRepository{db: db}
}

func (r *GormEmployeeRepository) Upsert(ctx context.Context, emp *model.Employee) error {
	var existing model.Employee
	lookupKey := emp.LarkUserID
	lookupColumn := "lark_user_id"
	if lookupKey == "" {
		lookupKey = emp.Email
		lookupColumn = "email"
	}
	if lookupKey == "" {
		return nil
	}

	err := r.db.WithContext(ctx).Where(lookupColumn+" = ?", lookupKey).First(&existing).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return r.db.WithContext(ctx).Create(emp).Error
		}
		return err
	}
	// Update existing record, preserving timestamps
	return r.db.WithContext(ctx).Model(&existing).Updates(map[string]interface{}{
		"name":           emp.Name,
		"email":          emp.Email,
		"department_id":  emp.DepartmentID,
		"department_ids": emp.DepartmentIDs,
		"status":         emp.Status,
		"avatar_url":     emp.AvatarURL,
		"job_title":      emp.JobTitle,
	}).Error
}

func (r *GormEmployeeRepository) List(ctx context.Context) ([]model.Employee, error) {
	var emps []model.Employee
	err := r.db.WithContext(ctx).
		Where("status = ?", "active").
		Order("name asc").
		Find(&emps).Error
	return emps, err
}

func (r *GormEmployeeRepository) FindByID(ctx context.Context, id uint64) (*model.Employee, error) {
	var emp model.Employee
	err := r.db.WithContext(ctx).First(&emp, id).Error
	if err != nil {
		return nil, err
	}
	return &emp, nil
}

func (r *GormEmployeeRepository) ListByDepartment(ctx context.Context, departmentID string) ([]model.Employee, error) {
	var emps []model.Employee
	query := r.db.WithContext(ctx).Where("department_id = ?", departmentID).
		Or("JSON_CONTAINS(department_ids, JSON_QUOTE(?))", departmentID)
	err := query.Find(&emps).Error
	return emps, err
}

func (r *GormEmployeeRepository) FindByEmail(ctx context.Context, email string) (*model.Employee, error) {
	normalizedEmail := strings.ToLower(strings.TrimSpace(email))
	var emp model.Employee
	err := r.db.WithContext(ctx).Where("LOWER(TRIM(email)) = ?", normalizedEmail).First(&emp).Error
	if err != nil {
		return nil, err
	}
	return &emp, nil
}

func (r *GormEmployeeRepository) FindByEmails(ctx context.Context, emails []string) ([]model.Employee, error) {
	var emps []model.Employee
	if len(emails) == 0 {
		return emps, nil
	}
	normalizedEmails := make([]string, 0, len(emails))
	seen := make(map[string]struct{}, len(emails))
	for _, email := range emails {
		normalized := strings.ToLower(strings.TrimSpace(email))
		if normalized == "" {
			continue
		}
		if _, exists := seen[normalized]; exists {
			continue
		}
		seen[normalized] = struct{}{}
		normalizedEmails = append(normalizedEmails, normalized)
	}
	if len(normalizedEmails) == 0 {
		return emps, nil
	}
	err := r.db.WithContext(ctx).
		Where("LOWER(TRIM(email)) IN ?", normalizedEmails).
		Find(&emps).Error
	return emps, err
}

func (r *GormEmployeeRepository) Search(ctx context.Context, query string) ([]model.Employee, error) {
	var emps []model.Employee
	likeQuery := "%" + query + "%"
	err := r.db.WithContext(ctx).
		Where("name LIKE ? OR email LIKE ? OR lark_user_id LIKE ? OR department_id LIKE ?", likeQuery, likeQuery, likeQuery, likeQuery).
		Limit(20).
		Find(&emps).Error
	return emps, err
}

func (r *GormEmployeeRepository) SearchActive(ctx context.Context, query string) ([]model.Employee, error) {
	var emps []model.Employee
	likeQuery := "%" + query + "%"
	err := r.db.WithContext(ctx).
		Where("status = ?", "active").
		Where("name LIKE ? OR email LIKE ? OR lark_user_id LIKE ? OR department_id LIKE ?", likeQuery, likeQuery, likeQuery, likeQuery).
		Order("name asc").
		Limit(20).
		Find(&emps).Error
	return emps, err
}

type GormOrgSyncRunRepository struct {
	db *gorm.DB
}

func NewOrgSyncRunRepository(db *gorm.DB) OrgSyncRunRepository {
	return &GormOrgSyncRunRepository{db: db}
}

func (r *GormOrgSyncRunRepository) Create(ctx context.Context, run *model.OrgSyncRun) error {
	return r.db.WithContext(ctx).Create(run).Error
}

func (r *GormOrgSyncRunRepository) Update(ctx context.Context, run *model.OrgSyncRun) error {
	return r.db.WithContext(ctx).Save(run).Error
}

func (r *GormOrgSyncRunRepository) ListRecent(ctx context.Context, limit int) ([]model.OrgSyncRun, error) {
	if limit <= 0 || limit > 100 {
		limit = 20
	}
	var runs []model.OrgSyncRun
	err := r.db.WithContext(ctx).Order("started_at desc").Limit(limit).Find(&runs).Error
	return runs, err
}

func (r *GormOrgSyncRunRepository) LatestSuccessful(ctx context.Context) (*model.OrgSyncRun, error) {
	var run model.OrgSyncRun
	err := r.db.WithContext(ctx).
		Where("status = ? AND finished_at IS NOT NULL", "success").
		Order("finished_at desc").
		First(&run).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, err
	}
	return &run, nil
}
