package identity

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"sort"
	"strings"

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

var (
	ErrIdentityConflict = errors.New("identity already exists")
	permissionPattern   = regexp.MustCompile(`^[a-z][a-z0-9_.:-]{2,127}$`)
)

type ManagementHTTPHandler struct {
	service *Service
	mux     *http.ServeMux
}

type identityInput struct {
	Login        string   `json:"login"`
	DisplayName  string   `json:"display_name"`
	Role         string   `json:"role"`
	Password     *string  `json:"password"`
	Permissions  []string `json:"permissions"`
	Active       *bool    `json:"active"`
	DepartmentID *string  `json:"department_id"`
}

func NewManagementHTTPHandler(service *Service) *ManagementHTTPHandler {
	h := &ManagementHTTPHandler{service: service, mux: http.NewServeMux()}
	h.mux.HandleFunc("GET /api/v1/admin/identities/admins", h.list(KindAdmin))
	h.mux.HandleFunc("POST /api/v1/admin/identities/admins", h.create(KindAdmin))
	h.mux.HandleFunc("PUT /api/v1/admin/identities/admins/{identity_id}", h.update(KindAdmin))
	h.mux.HandleFunc("GET /api/v1/admin/identities/portal-users", h.list(KindPortal))
	h.mux.HandleFunc("POST /api/v1/admin/identities/portal-users", h.create(KindPortal))
	h.mux.HandleFunc("PUT /api/v1/admin/identities/portal-users/{identity_id}", h.update(KindPortal))
	h.mux.HandleFunc("GET /api/v1/admin/departments", h.listDepartments)
	h.mux.HandleFunc("POST /api/v1/admin/departments", h.createDepartment)
	h.mux.HandleFunc("PUT /api/v1/admin/departments/{department_id}", h.updateDepartment)
	h.registerOIDC()
	return h
}

func (h *ManagementHTTPHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	h.mux.ServeHTTP(writer, request)
}

func (h *ManagementHTTPHandler) list(kind Kind) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		if _, ok := h.requirePermission(writer, request); !ok {
			return
		}
		accounts, err := h.service.repository.ListIdentities(request.Context(), kind)
		if err != nil {
			h.writeError(writer, err)
			return
		}
		items := make([]map[string]any, 0, len(accounts))
		for _, account := range accounts {
			items = append(items, managementView(account))
		}
		apiresponse.OK(writer, items)
	}
}

func (h *ManagementHTTPHandler) create(kind Kind) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		actor, ok := h.requirePermission(writer, request)
		if !ok {
			return
		}
		input, account, password, ok := h.decode(writer, request, kind, true)
		if !ok {
			return
		}
		_ = input
		passwordHash, err := h.service.hasher.Hash(password)
		if err != nil {
			h.writeError(writer, err)
			return
		}
		account.PasswordHash = passwordHash
		created, err := h.service.repository.CreateIdentity(request.Context(), account, actor.ID)
		if err != nil {
			h.writeError(writer, err)
			return
		}
		apiresponse.OK(writer, managementView(created))
	}
}

func (h *ManagementHTTPHandler) update(kind Kind) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		actor, ok := h.requirePermission(writer, request)
		if !ok {
			return
		}
		_, account, password, ok := h.decode(writer, request, kind, false)
		if !ok {
			return
		}
		account.ID = request.PathValue("identity_id")
		current, err := h.service.findByID(request.Context(), kind, account.ID)
		if err != nil {
			h.writeError(writer, err)
			return
		}
		if kind == KindAdmin && actor.ID == current.ID && (account.Role != current.Role || !account.Active) {
			apiresponse.Error(writer, http.StatusConflict, "不能停用自身账号或修改自身角色")
			return
		}
		var passwordHash *string
		if password != "" {
			hash, err := h.service.hasher.Hash(password)
			if err != nil {
				h.writeError(writer, err)
				return
			}
			passwordHash = &hash
		}
		updated, err := h.service.repository.UpdateIdentity(request.Context(), account, passwordHash, actor.ID)
		if err != nil {
			h.writeError(writer, err)
			return
		}
		apiresponse.OK(writer, managementView(updated))
	}
}

func (h *ManagementHTTPHandler) decode(writer http.ResponseWriter, request *http.Request, kind Kind, creating bool) (identityInput, Account, string, bool) {
	var input identityInput
	decoder := json.NewDecoder(http.MaxBytesReader(writer, request.Body, 1<<20))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&input); err != nil {
		apiresponse.Error(writer, http.StatusBadRequest, "请求格式无效")
		return input, Account{}, "", false
	}
	input.Login = strings.ToLower(strings.TrimSpace(input.Login))
	input.DisplayName = strings.TrimSpace(input.DisplayName)
	input.Role = strings.ToLower(strings.TrimSpace(input.Role))
	if input.Role == "" {
		if kind == KindPortal {
			input.Role = "member"
		} else {
			input.Role = "operator"
		}
	}
	if len(input.Login) < 2 || len(input.Login) > 128 || len(input.DisplayName) > 64 {
		apiresponse.Error(writer, http.StatusBadRequest, "账号或显示名称格式无效")
		return input, Account{}, "", false
	}
	if kind == KindAdmin && input.Role != "superadmin" && input.Role != "operator" && input.Role != "auditor" {
		apiresponse.Error(writer, http.StatusBadRequest, "管理员角色无效")
		return input, Account{}, "", false
	}
	if kind == KindPortal && input.Role != "member" {
		apiresponse.Error(writer, http.StatusBadRequest, "门户角色无效")
		return input, Account{}, "", false
	}
	permissions, err := normalizePermissions(input.Permissions)
	if err != nil {
		apiresponse.Error(writer, http.StatusBadRequest, err.Error())
		return input, Account{}, "", false
	}
	password := ""
	if input.Password != nil {
		password = *input.Password
	}
	if creating && len(password) < 12 || password != "" && len(password) < 12 || len(password) > 1024 {
		apiresponse.Error(writer, http.StatusBadRequest, "口令长度必须为 12 至 1024 个字符")
		return input, Account{}, "", false
	}
	active := true
	if input.Active != nil {
		active = *input.Active
	}
	var departmentID *string
	if input.DepartmentID != nil && strings.TrimSpace(*input.DepartmentID) != "" {
		value := strings.TrimSpace(*input.DepartmentID)
		department, err := h.service.repository.GetDepartment(request.Context(), value)
		if err != nil || !department.Active {
			apiresponse.Error(writer, http.StatusBadRequest, "所选部门不存在或已停用")
			return input, Account{}, "", false
		}
		departmentID = &value
	}
	return input, Account{
		Kind: kind, Login: input.Login, DisplayName: input.DisplayName,
		Role: input.Role, Permissions: permissions, Active: active, DepartmentID: departmentID,
	}, password, true
}

func (h *ManagementHTTPHandler) requirePermission(writer http.ResponseWriter, request *http.Request) (Account, bool) {
	account, err := h.service.Authenticate(request.Context(), KindAdmin, request.Header.Get("Authorization"))
	if err != nil {
		apiresponse.Error(writer, http.StatusUnauthorized, "登录状态无效或已过期")
		return Account{}, false
	}
	if !HasPermission(account, PermissionIdentityManage) {
		apiresponse.Error(writer, http.StatusForbidden, "缺少身份管理权限")
		return Account{}, false
	}
	return account, true
}

func (h *ManagementHTTPHandler) writeError(writer http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, ErrNotFound):
		apiresponse.Error(writer, http.StatusNotFound, "账号不存在")
	case errors.Is(err, ErrIdentityConflict):
		apiresponse.Error(writer, http.StatusConflict, "账号已存在")
	case errors.Is(err, ErrUnavailable):
		apiresponse.Error(writer, http.StatusServiceUnavailable, "身份管理服务暂不可用")
	default:
		apiresponse.Error(writer, http.StatusInternalServerError, "身份管理操作失败")
	}
}

func normalizePermissions(values []string) ([]string, error) {
	seen := make(map[string]struct{})
	result := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.ToLower(strings.TrimSpace(value))
		if value != "*" && !permissionPattern.MatchString(value) {
			return nil, fmt.Errorf("权限字符串 %q 格式无效", value)
		}
		if _, exists := seen[value]; exists {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	sort.Strings(result)
	return result, nil
}

func managementView(account Account) map[string]any {
	return map[string]any{
		"id": account.ID, "kind": account.Kind, "login": account.Login,
		"display_name": account.DisplayName, "role": account.Role,
		"permissions": account.Permissions, "effective_permissions": EffectivePermissions(account),
		"active": account.Active, "auth_source": account.AuthSource,
		"totp_enabled": account.TOTPEnabled, "locked_until": account.LockedUntil,
		"created_at": account.CreatedAt, "updated_at": account.UpdatedAt,
		"department_id": account.DepartmentID, "department_name": account.DepartmentName,
	}
}

func (r *Repository) ListIdentities(ctx context.Context, kind Kind) ([]Account, error) {
	if r.pool == nil {
		return nil, ErrUnavailable
	}
	query := `
        SELECT id::text, username, display_name, role, permissions, active,
               totp_enabled, locked_until, 'local', created_at, updated_at
          FROM gateway.admin_accounts ORDER BY lower(username)`
	if kind == KindPortal {
		query = `
        SELECT u.id::text, u.account, u.name, u.role, u.permissions, u.active,
               u.totp_enabled, u.locked_until, u.auth_source, u.created_at, u.updated_at,
               u.department_id::text, COALESCE(d.name, '')
          FROM gateway.portal_users u
          LEFT JOIN gateway.departments d ON d.id = u.department_id
         ORDER BY lower(u.account)`
	}
	rows, err := r.pool.Query(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer rows.Close()
	accounts := make([]Account, 0)
	for rows.Next() {
		account := Account{Kind: kind}
		arguments := []any{
			&account.ID, &account.Login, &account.DisplayName, &account.Role,
			&account.Permissions, &account.Active, &account.TOTPEnabled,
			&account.LockedUntil, &account.AuthSource, &account.CreatedAt, &account.UpdatedAt,
		}
		if kind == KindPortal {
			arguments = append(arguments, &account.DepartmentID, &account.DepartmentName)
		}
		if err := rows.Scan(arguments...); err != nil {
			return nil, fmt.Errorf("%w: %v", ErrUnavailable, err)
		}
		accounts = append(accounts, account)
	}
	return accounts, mapRepositoryError(rows.Err())
}

func (r *Repository) CreateIdentity(ctx context.Context, account Account, actorID string) (Account, error) {
	if r.pool == nil {
		return Account{}, ErrUnavailable
	}
	id, err := platformid.NewUUID()
	if err != nil {
		return Account{}, err
	}
	eventID, err := platformid.NewUUID()
	if err != nil {
		return Account{}, err
	}
	account.ID = id
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return Account{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer func() { _ = tx.Rollback(ctx) }()
	if account.Kind == KindAdmin {
		err = tx.QueryRow(ctx, `
            INSERT INTO gateway.admin_accounts
                        (id, username, display_name, role, permissions, password_hash, active)
                 VALUES ($1, $2, $3, $4, $5, $6, $7)
              RETURNING created_at, updated_at`, account.ID, account.Login, account.DisplayName,
			account.Role, account.Permissions, account.PasswordHash, account.Active,
		).Scan(&account.CreatedAt, &account.UpdatedAt)
	} else {
		account.AuthSource = "local"
		err = tx.QueryRow(ctx, `
            INSERT INTO gateway.portal_users
			            (id, account, name, role, permissions, password_hash, auth_source, active, department_id)
			     VALUES ($1, $2, $3, $4, $5, $6, 'local', $7, $8)
              RETURNING created_at, updated_at`, account.ID, account.Login, account.DisplayName,
			account.Role, account.Permissions, account.PasswordHash, account.Active, account.DepartmentID,
		).Scan(&account.CreatedAt, &account.UpdatedAt)
	}
	if err != nil {
		return Account{}, mapManagementError(err)
	}
	payload, _ := json.Marshal(map[string]any{"identity_id": account.ID, "kind": account.Kind, "actor_id": actorID})
	if _, err := tx.Exec(ctx, `
        INSERT INTO gateway.outbox_events
                    (event_id, event_type, event_version, aggregate_type, aggregate_id, payload)
             VALUES ($1, 'identity.created', 1, 'identity', $2, $3)`, eventID, account.ID, payload); err != nil {
		return Account{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	if err := tx.Commit(ctx); err != nil {
		return Account{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	return account, nil
}

func (r *Repository) UpdateIdentity(ctx context.Context, account Account, passwordHash *string, actorID string) (Account, error) {
	if r.pool == nil {
		return Account{}, ErrUnavailable
	}
	eventID, err := platformid.NewUUID()
	if err != nil {
		return Account{}, err
	}
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return Account{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer func() { _ = tx.Rollback(ctx) }()
	if account.Kind == KindAdmin {
		err = tx.QueryRow(ctx, `
            UPDATE gateway.admin_accounts
               SET username = $2, display_name = $3, role = $4, permissions = $5,
			       active = $6, password_hash = COALESCE($7, password_hash),
                   updated_at = clock_timestamp()
             WHERE id = $1
         RETURNING totp_enabled, locked_until, created_at, updated_at`, account.ID, account.Login,
			account.DisplayName, account.Role, account.Permissions, account.Active, passwordHash,
		).Scan(&account.TOTPEnabled, &account.LockedUntil, &account.CreatedAt, &account.UpdatedAt)
	} else {
		account.AuthSource = "local"
		err = tx.QueryRow(ctx, `
            UPDATE gateway.portal_users
               SET account = $2, name = $3, role = $4, permissions = $5,
                   active = $6, password_hash = COALESCE($7, password_hash), department_id = $8,
                   updated_at = clock_timestamp()
             WHERE id = $1
         RETURNING auth_source, totp_enabled, locked_until, created_at, updated_at`, account.ID, account.Login,
			account.DisplayName, account.Role, account.Permissions, account.Active, passwordHash, account.DepartmentID,
		).Scan(&account.AuthSource, &account.TOTPEnabled, &account.LockedUntil, &account.CreatedAt, &account.UpdatedAt)
	}
	if err != nil {
		return Account{}, mapManagementError(err)
	}
	payload, _ := json.Marshal(map[string]any{"identity_id": account.ID, "kind": account.Kind, "actor_id": actorID})
	if _, err := tx.Exec(ctx, `
        INSERT INTO gateway.outbox_events
                    (event_id, event_type, event_version, aggregate_type, aggregate_id, payload)
             VALUES ($1, 'identity.updated', 1, 'identity', $2, $3)`, eventID, account.ID, payload); err != nil {
		return Account{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	if err := tx.Commit(ctx); err != nil {
		return Account{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	return account, nil
}

func mapManagementError(err error) error {
	if errors.Is(err, pgx.ErrNoRows) {
		return ErrNotFound
	}
	var pgError *pgconn.PgError
	if errors.As(err, &pgError) && pgError.Code == "23505" {
		return ErrIdentityConflict
	}
	if err != nil {
		return fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	return nil
}
