package identity

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

	"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 (
	ErrDepartmentConflict = errors.New("department already exists")
	ErrDepartmentInUse    = errors.New("department is in use")
	ErrDepartmentCycle    = errors.New("department hierarchy cycle")
	departmentCodePattern = regexp.MustCompile(`^[a-z][a-z0-9_-]{1,63}$`)
)

type Department struct {
	ID          string    `json:"id"`
	Code        string    `json:"code"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	ParentID    *string   `json:"parent_id"`
	ParentName  string    `json:"parent_name,omitempty"`
	Active      bool      `json:"active"`
	UserCount   int       `json:"user_count"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

type departmentInput struct {
	Code        string  `json:"code"`
	Name        string  `json:"name"`
	Description string  `json:"description"`
	ParentID    *string `json:"parent_id"`
	Active      *bool   `json:"active"`
}

func (h *ManagementHTTPHandler) listDepartments(writer http.ResponseWriter, request *http.Request) {
	if _, ok := h.requirePermission(writer, request); !ok {
		return
	}
	departments, err := h.service.repository.ListDepartments(request.Context())
	if err != nil {
		h.writeDepartmentError(writer, err)
		return
	}
	apiresponse.OK(writer, departments)
}

func (h *ManagementHTTPHandler) createDepartment(writer http.ResponseWriter, request *http.Request) {
	actor, ok := h.requirePermission(writer, request)
	if !ok {
		return
	}
	input, department, ok := decodeDepartment(writer, request)
	if !ok {
		return
	}
	_ = input
	created, err := h.service.repository.CreateDepartment(request.Context(), department, actor.ID)
	if err != nil {
		h.writeDepartmentError(writer, err)
		return
	}
	apiresponse.OK(writer, created)
}

func (h *ManagementHTTPHandler) updateDepartment(writer http.ResponseWriter, request *http.Request) {
	actor, ok := h.requirePermission(writer, request)
	if !ok {
		return
	}
	_, department, ok := decodeDepartment(writer, request)
	if !ok {
		return
	}
	department.ID = request.PathValue("department_id")
	updated, err := h.service.repository.UpdateDepartment(request.Context(), department, actor.ID)
	if err != nil {
		h.writeDepartmentError(writer, err)
		return
	}
	apiresponse.OK(writer, updated)
}

func decodeDepartment(writer http.ResponseWriter, request *http.Request) (departmentInput, Department, bool) {
	var input departmentInput
	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, Department{}, false
	}
	input.Code = strings.ToLower(strings.TrimSpace(input.Code))
	input.Name = strings.TrimSpace(input.Name)
	input.Description = strings.TrimSpace(input.Description)
	if !departmentCodePattern.MatchString(input.Code) || input.Name == "" || len(input.Name) > 128 || len(input.Description) > 1024 {
		apiresponse.Error(writer, http.StatusBadRequest, "部门代码、名称或描述格式无效")
		return input, Department{}, false
	}
	var parentID *string
	if input.ParentID != nil && strings.TrimSpace(*input.ParentID) != "" {
		value := strings.TrimSpace(*input.ParentID)
		parentID = &value
	}
	active := true
	if input.Active != nil {
		active = *input.Active
	}
	return input, Department{Code: input.Code, Name: input.Name, Description: input.Description, ParentID: parentID, Active: active}, true
}

func (h *ManagementHTTPHandler) writeDepartmentError(writer http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, ErrNotFound):
		apiresponse.Error(writer, http.StatusNotFound, "部门不存在")
	case errors.Is(err, ErrDepartmentConflict):
		apiresponse.Error(writer, http.StatusConflict, "部门代码已存在")
	case errors.Is(err, ErrDepartmentCycle):
		apiresponse.Error(writer, http.StatusConflict, "部门层级不能形成循环")
	case errors.Is(err, ErrDepartmentInUse):
		apiresponse.Error(writer, http.StatusConflict, "部门仍包含启用用户或启用子部门，不能停用")
	case errors.Is(err, ErrUnavailable):
		apiresponse.Error(writer, http.StatusServiceUnavailable, "部门服务暂不可用")
	default:
		apiresponse.Error(writer, http.StatusBadRequest, "部门操作失败")
	}
}

func (r *Repository) ListDepartments(ctx context.Context) ([]Department, error) {
	if r.pool == nil {
		return nil, ErrUnavailable
	}
	rows, err := r.pool.Query(ctx, `
        SELECT d.id::text, d.code, d.name, d.description, d.parent_id::text,
               COALESCE(p.name, ''), d.active,
               count(u.id) FILTER (WHERE u.active), d.created_at, d.updated_at
          FROM gateway.departments d
          LEFT JOIN gateway.departments p ON p.id = d.parent_id
          LEFT JOIN gateway.portal_users u ON u.department_id = d.id
         GROUP BY d.id, p.name
         ORDER BY d.code`)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer rows.Close()
	departments := make([]Department, 0)
	for rows.Next() {
		var department Department
		if err := rows.Scan(&department.ID, &department.Code, &department.Name, &department.Description,
			&department.ParentID, &department.ParentName, &department.Active, &department.UserCount,
			&department.CreatedAt, &department.UpdatedAt); err != nil {
			return nil, fmt.Errorf("%w: %v", ErrUnavailable, err)
		}
		departments = append(departments, department)
	}
	return departments, mapRepositoryError(rows.Err())
}

func (r *Repository) GetDepartment(ctx context.Context, id string) (Department, error) {
	if r.pool == nil {
		return Department{}, ErrUnavailable
	}
	var department Department
	err := r.pool.QueryRow(ctx, `
        SELECT id::text, code, name, description, parent_id::text, active, created_at, updated_at
          FROM gateway.departments WHERE id = $1`, id).Scan(
		&department.ID, &department.Code, &department.Name, &department.Description,
		&department.ParentID, &department.Active, &department.CreatedAt, &department.UpdatedAt,
	)
	return department, mapRepositoryError(err)
}

func (r *Repository) CreateDepartment(ctx context.Context, department Department, actorID string) (Department, error) {
	id, err := platformid.NewUUID()
	if err != nil {
		return Department{}, err
	}
	department.ID = id
	return r.storeDepartment(ctx, department, actorID, true)
}

func (r *Repository) UpdateDepartment(ctx context.Context, department Department, actorID string) (Department, error) {
	return r.storeDepartment(ctx, department, actorID, false)
}

func (r *Repository) storeDepartment(ctx context.Context, department Department, actorID string, creating bool) (Department, error) {
	if r.pool == nil {
		return Department{}, ErrUnavailable
	}
	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return Department{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer func() { _ = tx.Rollback(ctx) }()
	if department.ParentID != nil {
		var parentActive bool
		if err := tx.QueryRow(ctx, `SELECT active FROM gateway.departments WHERE id = $1`, *department.ParentID).Scan(&parentActive); err != nil {
			return Department{}, mapRepositoryError(err)
		}
		if !parentActive {
			return Department{}, ErrDepartmentInUse
		}
	}
	if !creating && department.ParentID != nil {
		var cycle bool
		if err := tx.QueryRow(ctx, `
            WITH RECURSIVE descendants AS (
                SELECT id FROM gateway.departments WHERE parent_id = $1
                UNION ALL
                SELECT d.id FROM gateway.departments d JOIN descendants x ON d.parent_id = x.id
            )
            SELECT $2::uuid = $1::uuid OR EXISTS (SELECT 1 FROM descendants WHERE id = $2)`,
			department.ID, *department.ParentID).Scan(&cycle); err != nil {
			return Department{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
		}
		if cycle {
			return Department{}, ErrDepartmentCycle
		}
	}
	if !creating && !department.Active {
		var inUse bool
		if err := tx.QueryRow(ctx, `
            SELECT EXISTS (SELECT 1 FROM gateway.portal_users WHERE department_id = $1 AND active)
                OR EXISTS (SELECT 1 FROM gateway.departments WHERE parent_id = $1 AND active)`, department.ID).Scan(&inUse); err != nil {
			return Department{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
		}
		if inUse {
			return Department{}, ErrDepartmentInUse
		}
	}
	if creating {
		err = tx.QueryRow(ctx, `
            INSERT INTO gateway.departments (id, code, name, description, parent_id, active)
                 VALUES ($1, $2, $3, $4, $5, $6)
              RETURNING created_at, updated_at`, department.ID, department.Code, department.Name,
			department.Description, department.ParentID, department.Active).Scan(&department.CreatedAt, &department.UpdatedAt)
	} else {
		err = tx.QueryRow(ctx, `
            UPDATE gateway.departments
               SET code = $2, name = $3, description = $4, parent_id = $5,
                   active = $6, updated_at = clock_timestamp()
             WHERE id = $1
         RETURNING created_at, updated_at`, department.ID, department.Code, department.Name,
			department.Description, department.ParentID, department.Active).Scan(&department.CreatedAt, &department.UpdatedAt)
	}
	if err != nil {
		return Department{}, mapDepartmentError(err)
	}
	eventID, err := platformid.NewUUID()
	if err != nil {
		return Department{}, err
	}
	eventType := "department.updated"
	if creating {
		eventType = "department.created"
	}
	payload, _ := json.Marshal(map[string]any{"department_id": department.ID, "code": department.Code, "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, $2, 1, 'department', $3, $4)`, eventID, eventType, department.ID, payload); err != nil {
		return Department{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	if err := tx.Commit(ctx); err != nil {
		return Department{}, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	return department, nil
}

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