package routes

import (
	"bssapp-backend/auth"
	"bssapp-backend/internal/auditlog"
	"bssapp-backend/permissions"
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
)

func GetRolePermissionMatrix(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims, ok := auth.GetClaimsFromContext(r.Context())
		if !ok || claims == nil {
			http.Error(w, "unauthorized", 401)
			return
		}

		roleID, _ := strconv.Atoi(mux.Vars(r)["id"])

		rows, err := db.Query(`
		  SELECT
		    module_code,
		    action,
		    allowed
		  FROM mk_sys_role_permissions
		  WHERE role_id=$1
		`, roleID)

		if err != nil {
			http.Error(w, "db error", 500)
			return
		}
		defer rows.Close()

		var list []permissions.PermissionMatrixRow

		for rows.Next() {

			var row permissions.PermissionMatrixRow

			if err := rows.Scan(
				&row.Module,
				&row.Action,
				&row.Allowed,
			); err != nil {
				http.Error(w, "scan error", 500)
				return
			}

			row.Source = "role"

			list = append(list, row)
		}

		json.NewEncoder(w).Encode(list)
	}
}

func SaveRolePermissionMatrix(db *sql.DB) http.HandlerFunc {

	return func(w http.ResponseWriter, r *http.Request) {

		claims, ok := auth.GetClaimsFromContext(r.Context())
		if !ok || claims == nil {
			http.Error(w, "unauthorized", 401)
			return
		}

		roleID, _ := strconv.Atoi(mux.Vars(r)["id"])

		var list []permissions.PermissionMatrixRow

		if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
			http.Error(w, "bad payload", 400)
			return
		}

		repo := permissions.NewPermissionRepository(db)

		// ================= OLD =================

		oldRows, _ := repo.GetPermissionMatrixForRoles([]int{roleID})

		oldMap := map[string]bool{}

		for _, p := range oldRows {
			key := p.Module + ":" + p.Action
			oldMap[key] = p.Allowed
		}

		// ================= DIFF =================

		var changes []map[string]any

		for _, p := range list {

			key := p.Module + ":" + p.Action

			oldVal, ok := oldMap[key]
			if !ok {
				oldVal = false
			}

			if oldVal != p.Allowed {

				changes = append(changes, map[string]any{
					"module": p.Module,
					"action": p.Action,
					"before": oldVal,
					"after":  p.Allowed,
				})
			}
		}

		// ================= SAVE =================

		tx, err := db.Begin()
		if err != nil {
			http.Error(w, "tx error", 500)
			return
		}
		defer tx.Rollback()

		stmt, err := tx.Prepare(`
		 INSERT INTO mk_sys_role_permissions
		 (role_id,module_code,action,allowed)
		 VALUES ($1,$2,$3,$4)

		 ON CONFLICT
		 (role_id,module_code,action)
		 DO UPDATE SET allowed=EXCLUDED.allowed
		`)

		if err != nil {
			http.Error(w, "prepare error", 500)
			return
		}
		defer stmt.Close()

		for _, p := range list {

			if _, err := stmt.Exec(
				roleID,
				p.Module,
				p.Action,
				p.Allowed,
			); err != nil {
				http.Error(w, "exec error", 500)
				return
			}
		}

		if err := tx.Commit(); err != nil {
			http.Error(w, "commit error", 500)
			return
		}

		// ================= AUDIT =================

		if len(changes) > 0 {

			var roleCode string

			_ = db.QueryRow(`
				SELECT code FROM dfrole WHERE id=$1
			`, roleID).Scan(&roleCode)

			auditlog.Enqueue(r.Context(), auditlog.ActivityLog{

				ActionType:     "role_permission_change",
				ActionCategory: "role_permission",

				ActionTarget: fmt.Sprintf("/api/roles/%d/permissions", roleID),

				Description: "role permission matrix updated",

				Username: claims.Username,
				RoleCode: claims.RoleCode,
				DfUsrID:  int64(claims.ID),

				ChangeBefore: map[string]any{
					"permissions": oldRows,
				},

				ChangeAfter: map[string]any{
					"changes": changes,
				},

				IsSuccess: true,
			})
		}

		json.NewEncoder(w).Encode(map[string]bool{
			"success": true,
		})
	}
}
