package api

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
)

const divisionVisibilityAll = "*"

func normalizeDivisionCode(raw string) string {
	return strings.ToLower(strings.TrimSpace(raw))
}

func normalizeDivisionCodes(raw []string) []string {
	seen := map[string]struct{}{}
	out := []string{}
	for _, v := range raw {
		code := normalizeDivisionCode(v)
		if code == "" {
			continue
		}
		if _, ok := seen[code]; ok {
			continue
		}
		seen[code] = struct{}{}
		out = append(out, code)
	}
	return out
}

func (s *Server) userDivisionVisibility(ctx context.Context, userID uint64, role string) (bool, []string, error) {
	if strings.EqualFold(strings.TrimSpace(role), "superadmin") {
		return true, []string{}, nil
	}
	rows, err := s.DB.QueryContext(ctx,
		`SELECT division_code FROM user_division_visibility WHERE user_id = $1 ORDER BY division_code ASC`,
		userID,
	)
	if err != nil {
		return false, nil, err
	}
	defer rows.Close()
	codes := []string{}
	for rows.Next() {
		var code string
		if err := rows.Scan(&code); err != nil {
			return false, nil, err
		}
		code = normalizeDivisionCode(code)
		if code == divisionVisibilityAll {
			return true, []string{}, nil
		}
		if code != "" {
			codes = append(codes, code)
		}
	}
	return false, codes, rows.Err()
}

func (s *Server) adminUserDivisionVisibility(ctx context.Context, userID uint64, role string) (bool, []string) {
	all, codes, err := s.userDivisionVisibility(ctx, userID, role)
	if err != nil {
		return false, []string{}
	}
	return all, codes
}

func (s *Server) replaceUserDivisionVisibility(ctx context.Context, tx *sql.Tx, userID uint64, all bool, codes []string) error {
	if _, err := tx.ExecContext(ctx, `DELETE FROM user_division_visibility WHERE user_id = $1`, userID); err != nil {
		return err
	}
	if all {
		_, err := tx.ExecContext(ctx,
			`INSERT INTO user_division_visibility (user_id, division_code) VALUES ($1, $2)`,
			userID, divisionVisibilityAll,
		)
		return err
	}
	for _, code := range normalizeDivisionCodes(codes) {
		if _, err := tx.ExecContext(ctx,
			`INSERT INTO user_division_visibility (user_id, division_code) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
			userID, code,
		); err != nil {
			return err
		}
	}
	return nil
}

func divisionAllowedByVisibility(all bool, codes []string, division string) bool {
	if all {
		return true
	}
	d := normalizeDivisionCode(division)
	for _, code := range codes {
		if normalizeDivisionCode(code) == d {
			return true
		}
	}
	return false
}

func (s *Server) requestCanAccessDivision(ctx context.Context, c *gin.Context, division string) (bool, error) {
	if roleIsSuperadmin(c) {
		return true, nil
	}
	all, codes, err := s.userDivisionVisibility(ctx, userID(c), roleFromContext(c))
	if err != nil {
		return false, err
	}
	return divisionAllowedByVisibility(all, codes, division), nil
}

// divisionScope describes whether the user may see org-wide report data (calendar, analytics, dashboard).
type divisionScope struct {
	ViewOrg bool
	All     bool
	Codes   []string
}

func (s *Server) divisionScopeForUser(ctx context.Context, userID uint64, role string) (divisionScope, error) {
	if strings.EqualFold(strings.TrimSpace(role), "superadmin") {
		return divisionScope{ViewOrg: true, All: true}, nil
	}
	all, codes, err := s.userDivisionVisibility(ctx, userID, role)
	if err != nil {
		return divisionScope{}, err
	}
	if all {
		return divisionScope{ViewOrg: true, All: true}, nil
	}
	if len(codes) > 0 {
		return divisionScope{ViewOrg: true, Codes: codes}, nil
	}
	return divisionScope{}, nil
}

func (s *Server) divisionScopeFromContext(ctx context.Context, c *gin.Context) (divisionScope, error) {
	return s.divisionScopeForUser(ctx, userID(c), roleFromContext(c))
}

// reportsDivisionSQL appends AND … on reports.alias.division for scoped org reads.
// nextN is the next $ placeholder index. Returns clause, extra args, and updated nextN.
func reportsDivisionSQL(alias string, scope divisionScope, nextN int) (clause string, args []interface{}, n int) {
	n = nextN
	if !scope.ViewOrg || scope.All {
		return "", nil, n
	}
	if len(scope.Codes) == 0 {
		return " AND FALSE", nil, n
	}
	parts := make([]string, len(scope.Codes))
	args = []interface{}{}
	for i, code := range scope.Codes {
		parts[i] = "$" + strconv.Itoa(n)
		args = append(args, normalizeDivisionCode(code))
		n++
	}
	col := alias + ".division"
	clause = fmt.Sprintf(
		" AND LOWER(TRIM(COALESCE(%s, ''))) IN (%s)",
		col,
		strings.Join(parts, ","),
	)
	return clause, args, n
}
