package routes

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

	"github.com/gorilla/mux"
	"github.com/lib/pq"
)

type IdTitleOption struct {
	ID    string `json:"id"`
	Title string `json:"title"`
}
type Row struct {
	Route     string `json:"route"`
	CanAccess bool   `json:"can_access"`
}

type RoleDeptPermissionSummary struct {
	RoleID          int              `json:"role_id"`
	RoleTitle       string           `json:"role_title"`
	DepartmentCode  string           `json:"department_code"`
	DepartmentTitle string           `json:"department_title"`
	ModuleFlags     map[string]bool  `json:"module_flags"`
	Members         []RoleDeptMember `json:"members"`
}

type RoleDeptMember struct {
	ID       int64  `json:"id"`
	FullName string `json:"full_name"`
	Username string `json:"username"`
}

type AddRoleDeptMemberPayload struct {
	UserID int64 `json:"user_id"`
}

type ModuleLookupOption struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

type ModuleActionLookupOption struct {
	ModuleCode string `json:"module_code"`
	Action     string `json:"action"`
}

type RoleDeptPermissionListResponse struct {
	Modules       []ModuleLookupOption        `json:"modules"`
	ModuleActions []ModuleActionLookupOption  `json:"module_actions"`
	Rows          []RoleDeptPermissionSummary `json:"rows"`
}

type RoleDepartmentPermissionHandler struct {
	DB   *sql.DB
	Repo *permissions.RoleDepartmentPermissionRepo
}

func NewRoleDepartmentPermissionHandler(db *sql.DB) *RoleDepartmentPermissionHandler {

	return &RoleDepartmentPermissionHandler{
		DB:   db, // Added
		Repo: permissions.NewRoleDepartmentPermissionRepo(db),
	}
}

/* ======================================================
   LIST
====================================================== */

func (h *RoleDepartmentPermissionHandler) List(w http.ResponseWriter, r *http.Request) {

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

	search := strings.TrimSpace(r.URL.Query().Get("search"))

	modRows, err := h.DB.Query(queries.GetModuleLookup)
	if err != nil {
		http.Error(w, "module lookup error", http.StatusInternalServerError)
		return
	}
	defer modRows.Close()

	modules := make([]ModuleLookupOption, 0, 32)
	for modRows.Next() {
		var m ModuleLookupOption
		if err := modRows.Scan(&m.Value, &m.Label); err != nil {
			http.Error(w, "module lookup scan error", http.StatusInternalServerError)
			return
		}
		modules = append(modules, m)
	}

	if err := modRows.Err(); err != nil {
		http.Error(w, "module lookup rows error", http.StatusInternalServerError)
		return
	}

	actionRows, err := h.DB.Query(queries.GetModuleActionLookup)
	if err != nil {
		http.Error(w, "module action lookup error", http.StatusInternalServerError)
		return
	}
	defer actionRows.Close()

	moduleActions := make([]ModuleActionLookupOption, 0, 128)
	for actionRows.Next() {
		var a ModuleActionLookupOption
		if err := actionRows.Scan(&a.ModuleCode, &a.Action); err != nil {
			http.Error(w, "module action scan error", http.StatusInternalServerError)
			return
		}
		moduleActions = append(moduleActions, a)
	}

	if err := actionRows.Err(); err != nil {
		http.Error(w, "module action rows error", http.StatusInternalServerError)
		return
	}

	rows, err := h.DB.Query(queries.ListRoleDepartmentPermissionSets, search)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	list := make([]RoleDeptPermissionSummary, 0, 128)
	for rows.Next() {
		var item RoleDeptPermissionSummary
		var rawFlags []byte
		var rawMembers []byte
		if err := rows.Scan(
			&item.RoleID,
			&item.RoleTitle,
			&item.DepartmentCode,
			&item.DepartmentTitle,
			&rawFlags,
			&rawMembers,
		); err != nil {
			http.Error(w, "scan error", http.StatusInternalServerError)
			return
		}

		item.ModuleFlags = map[string]bool{}
		if len(rawFlags) > 0 {
			if err := json.Unmarshal(rawFlags, &item.ModuleFlags); err != nil {
				http.Error(w, "module flags parse error", http.StatusInternalServerError)
				return
			}
		}
		item.Members = make([]RoleDeptMember, 0)
		if len(rawMembers) > 0 {
			if err := json.Unmarshal(rawMembers, &item.Members); err != nil {
				http.Error(w, "members parse error", http.StatusInternalServerError)
				return
			}
		}
		list = append(list, item)
	}

	if err := rows.Err(); err != nil {
		http.Error(w, "rows error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(RoleDeptPermissionListResponse{
		Modules:       modules,
		ModuleActions: moduleActions,
		Rows:          list,
	})
}

/* ======================================================
   GET
====================================================== */

func (h *RoleDepartmentPermissionHandler) Get(w http.ResponseWriter, r *http.Request) {

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

	vars := mux.Vars(r)

	roleID, err := strconv.Atoi(vars["roleId"])
	if err != nil || roleID <= 0 {
		http.Error(w, "invalid roleId", http.StatusBadRequest)
		return
	}

	deptCode := vars["deptCode"]
	if deptCode == "" {
		http.Error(w, "invalid deptCode", http.StatusBadRequest)
		return
	}

	list, err := h.Repo.Get(roleID, deptCode)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(list)
}

/* ======================================================
   POST
====================================================== */

func (h *RoleDepartmentPermissionHandler) Save(w http.ResponseWriter, r *http.Request) {

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

	vars := mux.Vars(r)

	roleID, err := strconv.Atoi(vars["roleId"])
	if err != nil || roleID <= 0 {
		http.Error(w, "invalid roleId", http.StatusBadRequest)
		return
	}

	deptCode := vars["deptCode"]
	if deptCode == "" {
		http.Error(w, "invalid deptCode", http.StatusBadRequest)
		return
	}

	var list []permissions.RoleDepartmentPermission
	if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
		http.Error(w, "bad payload", http.StatusBadRequest)
		return
	}

	// ================= OLD =================
	oldRows, err := h.Repo.Get(roleID, deptCode)
	if err != nil {
		log.Println("OLD PERM LOAD ERROR:", err)
	}

	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 := oldMap[key]
		if oldVal != p.Allowed {
			changes = append(changes, map[string]any{
				"module": p.Module,
				"action": p.Action,
				"before": oldVal,
				"after":  p.Allowed,
			})
		}
	}

	// ================= SAVE =================
	if err := h.Repo.Save(roleID, deptCode, list); err != nil {
		http.Error(w, "save error", http.StatusInternalServerError)
		return
	}

	// ================= AUDIT =================
	if len(changes) > 0 {
		auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
			ActionType:     "role_department_permission_change",
			ActionCategory: "role_permission",
			ActionTarget: fmt.Sprintf(
				"/api/roles/%d/departments/%s/permissions",
				roleID,
				deptCode,
			),
			Description: "role+department permissions 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,
		})
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
}

func (h *RoleDepartmentPermissionHandler) Members(w http.ResponseWriter, r *http.Request) {
	claims, ok := auth.GetClaimsFromContext(r.Context())
	if !ok || claims == nil {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	roleID, deptCode, ok := roleDepartmentFromRequest(w, r)
	if !ok {
		return
	}

	members, err := listRoleDepartmentMembers(h.DB, roleID, deptCode)
	if err != nil {
		http.Error(w, "members query error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(members)
}

func (h *RoleDepartmentPermissionHandler) AddMember(w http.ResponseWriter, r *http.Request) {
	claims, ok := auth.GetClaimsFromContext(r.Context())
	if !ok || claims == nil {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	roleID, deptCode, ok := roleDepartmentFromRequest(w, r)
	if !ok {
		return
	}

	var payload AddRoleDeptMemberPayload
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil || payload.UserID <= 0 {
		http.Error(w, "invalid user_id", http.StatusBadRequest)
		return
	}

	tx, err := h.DB.BeginTx(r.Context(), nil)
	if err != nil {
		http.Error(w, "transaction start error", http.StatusInternalServerError)
		return
	}
	defer tx.Rollback()

	var userExists, roleExists, departmentExists bool
	if err := tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM mk_dfusr WHERE id=$1 AND is_active=TRUE)`, payload.UserID).Scan(&userExists); err != nil {
		http.Error(w, "user lookup error", http.StatusInternalServerError)
		return
	}
	if err := tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM dfrole WHERE id=$1)`, roleID).Scan(&roleExists); err != nil {
		http.Error(w, "role lookup error", http.StatusInternalServerError)
		return
	}
	if err := tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM mk_dprt WHERE code=$1 AND is_active=TRUE)`, deptCode).Scan(&departmentExists); err != nil {
		http.Error(w, "department lookup error", http.StatusInternalServerError)
		return
	}
	if !userExists || !roleExists || !departmentExists {
		http.Error(w, "user, role or department not found", http.StatusBadRequest)
		return
	}

	if _, err := tx.Exec(`
INSERT INTO dfrole_usr (dfusr_id, dfrole_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
`, payload.UserID, roleID); err != nil {
		http.Error(w, "user role insert error", http.StatusInternalServerError)
		return
	}

	if _, err := tx.Exec(`
UPDATE dfusr_dprt ud
SET is_active=TRUE
FROM mk_dprt d
WHERE ud.dfusr_id=$1
  AND ud.dprt_id=d.id
  AND d.code=$2
`, payload.UserID, deptCode); err != nil {
		http.Error(w, "user department update error", http.StatusInternalServerError)
		return
	}

	if _, err := tx.Exec(`
INSERT INTO dfusr_dprt (dfusr_id, dprt_id, is_active)
SELECT $1, d.id, TRUE
FROM mk_dprt d
WHERE d.code=$2
  AND NOT EXISTS (
    SELECT 1
    FROM dfusr_dprt ud
    WHERE ud.dfusr_id=$1
      AND ud.dprt_id=d.id
  )
`, payload.UserID, deptCode); err != nil {
		http.Error(w, "user department insert error", http.StatusInternalServerError)
		return
	}

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

	auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
		ActionType:     "role_department_member_add",
		ActionCategory: "role_permission",
		ActionTarget:   fmt.Sprintf("/api/roles/%d/departments/%s/members", roleID, deptCode),
		Description:    "user added to role+department group",
		Username:       claims.Username,
		RoleCode:       claims.RoleCode,
		DfUsrID:        int64(claims.ID),
		ChangeAfter: map[string]any{
			"user_id":         payload.UserID,
			"role_id":         roleID,
			"department_code": deptCode,
		},
		IsSuccess: true,
	})

	members, err := listRoleDepartmentMembers(h.DB, roleID, deptCode)
	if err != nil {
		http.Error(w, "members query error", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(members)
}

func roleDepartmentFromRequest(w http.ResponseWriter, r *http.Request) (int, string, bool) {
	vars := mux.Vars(r)
	roleID, err := strconv.Atoi(vars["roleId"])
	if err != nil || roleID <= 0 {
		http.Error(w, "invalid roleId", http.StatusBadRequest)
		return 0, "", false
	}
	deptCode := strings.TrimSpace(vars["deptCode"])
	if deptCode == "" {
		http.Error(w, "invalid deptCode", http.StatusBadRequest)
		return 0, "", false
	}
	return roleID, deptCode, true
}

func listRoleDepartmentMembers(db *sql.DB, roleID int, deptCode string) ([]RoleDeptMember, error) {
	rows, err := db.Query(queries.ListRoleDepartmentMembers, roleID, deptCode)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	members := make([]RoleDeptMember, 0, 16)
	for rows.Next() {
		var member RoleDeptMember
		if err := rows.Scan(&member.ID, &member.FullName, &member.Username); err != nil {
			return nil, err
		}
		members = append(members, member)
	}
	return members, rows.Err()
}

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

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

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

		type Row struct {
			Value string `json:"value"`
			Label string `json:"label"`
		}

		var list []Row

		for rows.Next() {

			var r Row

			if err := rows.Scan(
				&r.Value,
				&r.Label,
			); err != nil {
				http.Error(w, "scan error", 500)
				return
			}

			list = append(list, r)
		}

		json.NewEncoder(w).Encode(list)
	}
}
func GetRolesForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")

		rows, err := db.Query(queries.GetRolesForPermissionSelect)
		if err != nil {
			http.Error(w, "roles select error", 500)
			return
		}
		defer rows.Close()

		list := make([]IdTitleOption, 0)
		for rows.Next() {
			var o IdTitleOption
			if err := rows.Scan(&o.ID, &o.Title); err == nil {
				list = append(list, o)
			}
		}

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

func GetDepartmentsForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")

		rows, err := db.Query(queries.GetDepartmentsForPermissionSelect)
		if err != nil {
			http.Error(w, "departments select error", 500)
			return
		}
		defer rows.Close()

		list := make([]IdTitleOption, 0)
		for rows.Next() {
			var o IdTitleOption
			if err := rows.Scan(&o.ID, &o.Title); err == nil {
				list = append(list, o)
			}
		}

		_ = json.NewEncoder(w).Encode(list)
	}
}
func GetUsersForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		w.Header().Set("Content-Type", "application/json; charset=utf-8")

		rows, err := db.Query(queries.GetUserLookupForPermission)
		if err != nil {
			http.Error(w, "users lookup error", http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		list := make([]IdTitleOption, 0, 128)

		for rows.Next() {

			var o IdTitleOption

			if err := rows.Scan(&o.ID, &o.Title); err == nil {
				list = append(list, o)
			}
		}

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

func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Request) {

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

	userID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil || userID <= 0 {
		http.Error(w, "invalid id", http.StatusBadRequest)
		return
	}

	list, err := h.Repo.GetUserOverridesByUserID(userID)
	if err != nil {
		log.Println("USER OVERRIDE LOAD ERROR:", err)
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	_ = json.NewEncoder(w).Encode(list)
}

type routePermissionSeed struct {
	Module string
	Action string
	Path   string
}

type moduleActionSeed struct {
	Module string
	Action string
}

type permissionSnapshot struct {
	user     map[string]bool
	roleDept map[string]bool
	role     map[string]bool
}

func permissionKey(module, action string) string {
	return module + "|" + action
}

func loadPermissionSnapshot(
	db *sql.DB,
	userID int64,
	roleID int64,
	deptCodes []string,
) (permissionSnapshot, error) {
	snapshot := permissionSnapshot{
		user:     make(map[string]bool, 128),
		roleDept: make(map[string]bool, 128),
		role:     make(map[string]bool, 128),
	}

	userRows, err := db.Query(`
		SELECT module_code, action, allowed
		FROM mk_sys_user_permissions
		WHERE user_id = $1
	`, userID)
	if err != nil {
		return snapshot, err
	}
	for userRows.Next() {
		var module, action string
		var allowed bool
		if err := userRows.Scan(&module, &action, &allowed); err != nil {
			_ = userRows.Close()
			return snapshot, err
		}
		snapshot.user[permissionKey(module, action)] = allowed
	}
	if err := userRows.Err(); err != nil {
		_ = userRows.Close()
		return snapshot, err
	}
	_ = userRows.Close()

	if len(deptCodes) > 0 {
		roleDeptRows, err := db.Query(`
			SELECT module_code, action, BOOL_OR(allowed) AS allowed
			FROM vw_role_dept_permissions
			WHERE role_id = $1
			  AND department_code = ANY($2)
			GROUP BY module_code, action
		`,
			roleID,
			pq.Array(deptCodes),
		)
		if err != nil {
			return snapshot, err
		}
		for roleDeptRows.Next() {
			var module, action string
			var allowed bool
			if err := roleDeptRows.Scan(&module, &action, &allowed); err != nil {
				_ = roleDeptRows.Close()
				return snapshot, err
			}
			snapshot.roleDept[permissionKey(module, action)] = allowed
		}
		if err := roleDeptRows.Err(); err != nil {
			_ = roleDeptRows.Close()
			return snapshot, err
		}
		_ = roleDeptRows.Close()
	}

	roleRows, err := db.Query(`
		SELECT module_code, action, allowed
		FROM mk_sys_role_permissions
		WHERE role_id = $1
	`, roleID)
	if err != nil {
		return snapshot, err
	}
	for roleRows.Next() {
		var module, action string
		var allowed bool
		if err := roleRows.Scan(&module, &action, &allowed); err != nil {
			_ = roleRows.Close()
			return snapshot, err
		}
		snapshot.role[permissionKey(module, action)] = allowed
	}
	if err := roleRows.Err(); err != nil {
		_ = roleRows.Close()
		return snapshot, err
	}
	_ = roleRows.Close()

	return snapshot, nil
}

func resolvePermissionFromSnapshot(
	s permissionSnapshot,
	module string,
	action string,
) bool {
	key := permissionKey(module, action)

	if allowed, ok := s.user[key]; ok {
		return allowed
	}
	if allowed, ok := s.roleDept[key]; ok {
		return allowed
	}
	if allowed, ok := s.role[key]; ok {
		return allowed
	}
	return false
}

func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		w.Header().Set("Content-Type", "application/json; charset=utf-8")

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

		snapshot, err := loadPermissionSnapshot(
			db,
			int64(claims.ID),
			int64(claims.RoleID),
			claims.DepartmentCodes,
		)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		rows, err := db.Query(`
			SELECT DISTINCT
				module_code,
				action,
				path
			FROM mk_sys_routes
		`)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
		defer rows.Close()

		routeSeeds := make([]routePermissionSeed, 0, 128)
		for rows.Next() {
			var module, action, path string
			if err := rows.Scan(
				&module,
				&action,
				&path,
			); err != nil {
				continue
			}

			routeSeeds = append(routeSeeds, routePermissionSeed{
				Module: module,
				Action: action,
				Path:   path,
			})
		}
		if err := rows.Err(); err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		list := make([]Row, 0, len(routeSeeds))
		for _, route := range routeSeeds {
			list = append(list, Row{
				Route: route.Path,
				CanAccess: resolvePermissionFromSnapshot(
					snapshot,
					route.Module,
					route.Action,
				),
			})
		}

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

func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		w.Header().Set("Content-Type", "application/json; charset=utf-8")

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

		snapshot, err := loadPermissionSnapshot(
			db,
			int64(claims.ID),
			int64(claims.RoleID),
			claims.DepartmentCodes,
		)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		all, err := db.Query(`
			SELECT DISTINCT module_code, action
			FROM mk_sys_routes
		`)
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
		defer all.Close()

		moduleActions := make([]moduleActionSeed, 0, 128)
		for all.Next() {

			var m, a string
			if err := all.Scan(&m, &a); err != nil {
				continue
			}

			moduleActions = append(moduleActions, moduleActionSeed{
				Module: m,
				Action: a,
			})
		}
		if err := all.Err(); err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		type Row struct {
			Module  string `json:"module"`
			Action  string `json:"action"`
			Allowed bool   `json:"allowed"`
		}

		list := make([]Row, 0, len(moduleActions))
		for _, item := range moduleActions {
			list = append(list, Row{
				Module: item.Module,
				Action: item.Action,
				Allowed: resolvePermissionFromSnapshot(
					snapshot,
					item.Module,
					item.Action,
				),
			})
		}

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