package routes

import (
	"bssapp-backend/auth"
	"bssapp-backend/internal/auditlog"
	"bssapp-backend/internal/security"
	"bssapp-backend/models"
	"bssapp-backend/repository"
	"bssapp-backend/services"
	"database/sql"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
)

func FirstPasswordChangeHandler(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 {
			log.Printf(
				"FIRST_PASSWORD_CHANGE 401 reason=claims_missing method=%s path=%s",
				r.Method,
				r.URL.Path,
			)
			http.Error(w, "yetkisiz: token eksik veya geçersiz", http.StatusUnauthorized)
			return
		}

		var req struct {
			CurrentPassword string `json:"current_password"`
			NewPassword     string `json:"new_password"`
		}

		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, "geçersiz istek gövdesi", http.StatusBadRequest)
			return
		}

		req.CurrentPassword = strings.TrimSpace(req.CurrentPassword)
		req.NewPassword = strings.TrimSpace(req.NewPassword)
		if req.CurrentPassword == "" || req.NewPassword == "" {
			http.Error(w, "şifre alanları zorunludur", http.StatusUnprocessableEntity)
			return
		}

		mkRepo := repository.NewMkUserRepository(db)
		legacyRepo := repository.NewUserRepository(db)

		mkUser, mkErr := mkRepo.GetByID(claims.ID)
		hasMkUser := mkErr == nil
		if mkErr != nil && !errors.Is(mkErr, repository.ErrMkUserNotFound) {
			log.Printf(
				"FIRST_PASSWORD_CHANGE 500 reason=mk_lookup_failed user_id=%d err=%v",
				claims.ID,
				mkErr,
			)
			http.Error(w, "kullanıcı sorgulama hatası", http.StatusInternalServerError)
			return
		}

		var legacyUser *models.User

		// If user already exists in mk_dfusr with hash, verify against mk hash.
		// Otherwise verify against legacy dfusr password before migration.
		if hasMkUser && strings.TrimSpace(mkUser.PasswordHash) != "" {
			if bcrypt.CompareHashAndPassword(
				[]byte(mkUser.PasswordHash),
				[]byte(req.CurrentPassword),
			) != nil {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 401 reason=current_password_mismatch_mk user_id=%d username=%s",
					claims.ID,
					claims.Username,
				)
				http.Error(w, "mevcut şifre hatalı", http.StatusUnauthorized)
				return
			}
		} else {
			var err error
			legacyUser, err = legacyRepo.GetLegacyUserForLogin(claims.Username)
			if err != nil || legacyUser == nil || !legacyUser.IsActive {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 401 reason=legacy_user_not_found user_id=%d username=%s err=%v",
					claims.ID,
					claims.Username,
					err,
				)
				http.Error(w, "yetkisiz: kullanıcı bulunamadı", http.StatusUnauthorized)
				return
			}
			if !hasMkUser && int64(legacyUser.ID) != claims.ID {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 401 reason=legacy_id_mismatch user_id=%d legacy_id=%d username=%s",
					claims.ID,
					legacyUser.ID,
					claims.Username,
				)
				http.Error(w, "yetkisiz: kullanıcı bulunamadı", http.StatusUnauthorized)
				return
			}

			if !services.CheckPasswordWithLegacy(legacyUser, req.CurrentPassword) {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 401 reason=current_password_mismatch_legacy user_id=%d username=%s",
					claims.ID,
					claims.Username,
				)
				http.Error(w, "mevcut şifre hatalı", http.StatusUnauthorized)
				return
			}
		}

		if err := security.ValidatePassword(req.NewPassword); err != nil {
			http.Error(w, err.Error(), http.StatusUnprocessableEntity)
			return
		}

		hash, err := bcrypt.GenerateFromPassword(
			[]byte(req.NewPassword),
			bcrypt.DefaultCost,
		)
		if err != nil {
			http.Error(w, "şifre hash hatası", http.StatusInternalServerError)
			return
		}

		tx, err := db.Begin()
		if err != nil {
			http.Error(w, "işlem başlatılamadı", http.StatusInternalServerError)
			return
		}
		defer tx.Rollback()

		migratedFromLegacy := false

		if hasMkUser {
			res, err := tx.Exec(`
				UPDATE mk_dfusr
				SET
					password_hash = $1,
					force_password_change = false,
					password_updated_at = NOW(),
					updated_at = NOW()
				WHERE id = $2
			`, string(hash), claims.ID)
			if err != nil {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 500 reason=password_update_failed user_id=%d err=%v",
					claims.ID,
					err,
				)
				http.Error(w, "şifre güncellenemedi", http.StatusInternalServerError)
				return
			}

			affected, _ := res.RowsAffected()
			if affected == 0 {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 500 reason=password_update_no_rows user_id=%d",
					claims.ID,
				)
				http.Error(w, "şifre güncellenemedi", http.StatusInternalServerError)
				return
			}
		} else {
			if legacyUser == nil {
				// Defensive fallback, should not happen.
				legacyUser, err = legacyRepo.GetLegacyUserForLogin(claims.Username)
				if err != nil || legacyUser == nil {
					log.Printf(
						"FIRST_PASSWORD_CHANGE 500 reason=legacy_reload_failed user_id=%d username=%s err=%v",
						claims.ID,
						claims.Username,
						err,
					)
					http.Error(w, "legacy kullanıcı yeniden yüklenemedi", http.StatusInternalServerError)
					return
				}
				if !hasMkUser && int64(legacyUser.ID) != claims.ID {
					log.Printf(
						"FIRST_PASSWORD_CHANGE 500 reason=legacy_reload_id_mismatch user_id=%d legacy_id=%d username=%s",
						claims.ID,
						legacyUser.ID,
						claims.Username,
					)
					http.Error(w, "legacy kullanıcı yeniden yüklenemedi", http.StatusInternalServerError)
					return
				}
			}

			_, err = tx.Exec(`
				INSERT INTO mk_dfusr (
					id,
					username,
					email,
					full_name,
					mobile,
					address,
					is_active,
					password_hash,
					force_password_change,
					password_updated_at,
					created_at,
					updated_at
				)
				VALUES (
					$1,$2,$3,$4,$5,$6,$7,$8,false,NOW(),NOW(),NOW()
				)
				ON CONFLICT (id)
				DO UPDATE SET
					username = EXCLUDED.username,
					email = EXCLUDED.email,
					full_name = EXCLUDED.full_name,
					mobile = EXCLUDED.mobile,
					address = EXCLUDED.address,
					is_active = EXCLUDED.is_active,
					password_hash = EXCLUDED.password_hash,
					force_password_change = false,
					password_updated_at = NOW(),
					updated_at = NOW()
			`,
				int64(legacyUser.ID),
				strings.TrimSpace(legacyUser.Username),
				strings.TrimSpace(legacyUser.Email),
				strings.TrimSpace(legacyUser.FullName),
				strings.TrimSpace(legacyUser.Mobile),
				strings.TrimSpace(legacyUser.Address),
				legacyUser.IsActive,
				string(hash),
			)
			if err != nil {
				log.Printf(
					"FIRST_PASSWORD_CHANGE 500 reason=legacy_migration_failed user_id=%d username=%s err=%v",
					claims.ID,
					claims.Username,
					err,
				)
				http.Error(w, "legacy geçişi başarısız", http.StatusInternalServerError)
				return
			}

			migratedFromLegacy = true
		}

		if err := tx.Commit(); err != nil {
			log.Printf(
				"FIRST_PASSWORD_CHANGE 500 reason=tx_commit_failed user_id=%d err=%v",
				claims.ID,
				err,
			)
			http.Error(w, "işlem tamamlanamadı", http.StatusInternalServerError)
			return
		}

		_ = repository.NewRefreshTokenRepository(db).RevokeAllForUser(claims.ID)

		newClaims := auth.BuildClaimsFromUser(
			&models.MkUser{
				ID:                  claims.ID,
				Username:            claims.Username,
				RoleID:              claims.RoleID,
				RoleCode:            claims.RoleCode,
				DepartmentCodes:     claims.DepartmentCodes,
				V3Username:          claims.V3Username,
				V3UserGroup:         claims.V3UserGroup,
				SessionID:           claims.SessionID,
				ForcePasswordChange: false,
			},
			15*time.Minute,
		)

		newToken, err := auth.GenerateToken(
			newClaims,
			claims.Username,
			false,
		)
		if err != nil {
			http.Error(w, "token üretilemedi", http.StatusInternalServerError)
			return
		}

		source := "mk_password_update"
		if migratedFromLegacy {
			source = "legacy_migration_completed"
		}
		auditlog.ForcePasswordChangeCompleted(
			r.Context(),
			claims.ID,
			source,
		)

		_ = json.NewEncoder(w).Encode(map[string]any{
			"token": newToken,
			"user": map[string]any{
				"id":                    claims.ID,
				"username":              claims.Username,
				"force_password_change": false,
			},
			"migrated_from_legacy": migratedFromLegacy,
		})

		log.Printf(
			"FIRST_PASSWORD_CHANGE 200 user=%d role=%s migrated_from_legacy=%v",
			claims.ID,
			claims.RoleCode,
			migratedFromLegacy,
		)
	}
}
