package identity

import (
	"context"
	"errors"
	"fmt"
	"strings"

	platformid "aigateway.local/core/internal/platform/id"
	"github.com/jackc/pgx/v5"
)

// Role 是一个自定义角色定义(内置角色在代码中,不落库)。
type Role struct {
	ID          string   `json:"id"`
	Code        string   `json:"code"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Permissions []string `json:"permissions"`
	Builtin     bool     `json:"builtin"`
	CreatedBy   *string  `json:"created_by,omitempty"`
	CreatedAt   string   `json:"created_at"`
	UpdatedAt   string   `json:"updated_at"`
}

// ListRoles 返回全部角色(内置 + 自定义)。
func (r *Repository) ListRoles(ctx context.Context) ([]Role, error) {
	if r.pool == nil {
		return nil, ErrUnavailable
	}
	rows, err := r.pool.Query(ctx, `SELECT id::text,code,name,description,permissions,builtin,created_by::text,created_at,updated_at FROM gateway.roles ORDER BY builtin DESC,created_at`)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer rows.Close()
	items := []Role{}
	for rows.Next() {
		var item Role
		var createdBy *string
		if err := rows.Scan(&item.ID, &item.Code, &item.Name, &item.Description, &item.Permissions, &item.Builtin, &createdBy, &item.CreatedAt, &item.UpdatedAt); err != nil {
			return nil, err
		}
		item.CreatedBy = createdBy
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	// 合并代码内置角色(带可读名称)。
	builtinNames := map[string]string{"superadmin": "超级管理员", "operator": "运维操作员", "auditor": "审计员", "member": "普通成员"}
	for code, permissions := range rolePermissions {
		items = append(items, Role{ID: "builtin:" + code, Code: code, Name: builtinNames[code], Permissions: permissions, Builtin: true})
	}
	return items, nil
}

// FindRole 按 code 查询角色;builtin 角色由代码返回。
func (r *Repository) FindRole(ctx context.Context, code string) (Role, error) {
	code = strings.ToLower(strings.TrimSpace(code))
	if permissions, ok := rolePermissions[code]; ok {
		return Role{Code: code, Name: code, Permissions: permissions, Builtin: true}, nil
	}
	var item Role
	var createdBy *string
	err := r.pool.QueryRow(ctx, `SELECT id::text,code,name,description,permissions,builtin,created_by::text,created_at,updated_at FROM gateway.roles WHERE code=$1`, code).Scan(&item.ID, &item.Code, &item.Name, &item.Description, &item.Permissions, &item.Builtin, &createdBy, &item.CreatedAt, &item.UpdatedAt)
	if errors.Is(err, pgx.ErrNoRows) {
		return Role{}, ErrNotFound
	}
	if err != nil {
		return Role{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	item.CreatedBy = createdBy
	return item, nil
}

// SaveRole 创建或更新自定义角色;内置角色禁止修改。
func (r *Repository) SaveRole(ctx context.Context, id, code, name, description string, permissions []string, actorID string, create bool) (Role, error) {
	if r.pool == nil {
		return Role{}, ErrUnavailable
	}
	code = strings.ToLower(strings.TrimSpace(code))
	if _, builtin := rolePermissions[code]; builtin {
		return Role{}, errors.New("内置角色不可修改")
	}
	if create {
		id, err := platformid.NewUUID()
		if err != nil {
			return Role{}, err
		}
		_, err = r.pool.Exec(ctx, `INSERT INTO gateway.roles(id,code,name,description,permissions,created_by) VALUES($1,$2,$3,$4,$5,$6)`, id, code, strings.TrimSpace(name), strings.TrimSpace(description), permissions, actorID)
		if err != nil {
			return Role{}, mapRoleError(err)
		}
		return r.FindRole(ctx, code)
	}
	tag, err := r.pool.Exec(ctx, `UPDATE gateway.roles SET name=$2,description=$3,permissions=$4,updated_at=clock_timestamp() WHERE id=$1 AND NOT builtin`, id, strings.TrimSpace(name), strings.TrimSpace(description), permissions)
	if err != nil {
		return Role{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	if tag.RowsAffected() == 0 {
		return Role{}, ErrNotFound
	}
	var item Role
	item, err = r.FindRole(ctx, code)
	if err != nil {
		return Role{}, err
	}
	return item, nil
}

// DeleteRole 删除自定义角色(内置角色禁止)。
func (r *Repository) DeleteRole(ctx context.Context, id string) error {
	if r.pool == nil {
		return ErrUnavailable
	}
	tag, err := r.pool.Exec(ctx, `DELETE FROM gateway.roles WHERE id=$1 AND NOT builtin`, id)
	if err != nil {
		return fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	if tag.RowsAffected() == 0 {
		return ErrNotFound
	}
	return nil
}

func mapRoleError(err error) error {
	var pgError interface{ Code() string }
	if errors.As(err, &pgError) && pgError.Code() == "23505" {
		return ErrIdentityConflict
	}
	return fmt.Errorf("%w: %v", ErrUnavailable, err)
}
