package countryregion

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/roomclient"
	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/repository"
)

const CountryCodeRenameJobType = "country-code-rename"

var errCountryRenameJobConflict = errors.New("country code rename job already exists")

type countryCodeRenameJobStore interface {
	CreateJob(job *model.AdminJob) error
	ListActiveJobsByType(jobType string) ([]model.AdminJob, error)
	RenameCountryCodeAdminReferences(appCode string, oldCountryCode string, newCountryCode string, nowMS int64) (repository.CountryCodeAdminReferenceCounts, error)
}

type countryCodeRoomClient interface {
	RenameOwnerCountryCode(ctx context.Context, req roomclient.RenameOwnerCountryCodeRequest) (roomclient.RenameOwnerCountryCodeResult, error)
}

type countryCodeRenameResponse struct {
	JobID          uint   `json:"jobId"`
	Status         string `json:"status"`
	CountryID      int64  `json:"countryId"`
	OldCountryCode string `json:"oldCountryCode"`
	NewCountryCode string `json:"newCountryCode"`
}

type countryCodeRenameJobPayload struct {
	AppCode            string `json:"appCode"`
	CountryID          int64  `json:"countryId"`
	OldCountryCode     string `json:"oldCountryCode"`
	NewCountryCode     string `json:"newCountryCode"`
	CountryName        string `json:"countryName"`
	CountryDisplayName string `json:"countryDisplayName"`
	SortOrder          int32  `json:"sortOrder"`
	ISOAlpha3          string `json:"isoAlpha3"`
	ISONumeric         string `json:"isoNumeric"`
	PhoneCountryCode   string `json:"phoneCountryCode"`
	Flag               string `json:"flag"`
	ActorID            int64  `json:"actorId"`
	ActorName          string `json:"actorName"`
	RequestID          string `json:"requestId"`
}

type countryCodeRenameResult struct {
	CountryID       int64  `json:"countryId"`
	OldCountryCode  string `json:"oldCountryCode"`
	NewCountryCode  string `json:"newCountryCode"`
	Users           int64  `json:"users"`
	RegionMappings  int64  `json:"regionMappings"`
	RebuildTasks    int64  `json:"rebuildTasks"`
	Banners         int64  `json:"banners"`
	SplashScreens   int64  `json:"splashScreens"`
	RoomListEntries int64  `json:"roomListEntries"`
	RoomSnapshots   int64  `json:"roomSnapshots"`
	CompletedAtMs   int64  `json:"completedAtMs"`
}

type countryCodeRenameUserCounts struct {
	Users          int64
	RegionMappings int64
	RebuildTasks   int64
}

func (s *Service) RenameCountryCode(ctx context.Context, actorID int64, actorName string, countryID int64, requestID string, req renameCountryCodeRequest) (*countryCodeRenameResponse, error) {
	if s.userDB == nil {
		return nil, errors.New("user db is not configured")
	}
	if s.jobStore == nil {
		return nil, errors.New("job store is not configured")
	}
	req = normalizeRenameCountryCodeRequest(req)
	if countryID <= 0 || strings.TrimSpace(requestID) == "" || !validCountryRenameRequest(req) {
		return nil, errCountryInvalid
	}

	tx, err := s.userDB.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	country, err := queryCountryForUpdate(ctx, tx, countryID)
	if err != nil {
		return nil, err
	}
	oldCountryCode := strings.ToUpper(strings.TrimSpace(country.CountryCode))
	if oldCountryCode == req.CountryCode {
		return nil, errCountryInvalid
	}
	if err := ensureCountryCodeAvailable(ctx, tx, countryID, req.CountryCode); err != nil {
		return nil, err
	}
	if err := s.ensureNoActiveCountryCodeRenameJob(ctx, appctx.FromContext(ctx), countryID, oldCountryCode, req.CountryCode); err != nil {
		return nil, err
	}

	payload := countryCodeRenameJobPayload{
		AppCode:            appctx.FromContext(ctx),
		CountryID:          countryID,
		OldCountryCode:     oldCountryCode,
		NewCountryCode:     req.CountryCode,
		CountryName:        req.CountryName,
		CountryDisplayName: req.CountryDisplayName,
		SortOrder:          req.SortOrder,
		ISOAlpha3:          req.ISOAlpha3,
		ISONumeric:         req.ISONumeric,
		PhoneCountryCode:   req.PhoneCountryCode,
		Flag:               req.Flag,
		ActorID:            actorID,
		ActorName:          strings.TrimSpace(actorName),
		RequestID:          strings.TrimSpace(requestID),
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}
	job := model.AdminJob{
		Type:          CountryCodeRenameJobType,
		Status:        model.JobStatusPending,
		PayloadJSON:   string(body),
		CreatedBy:     uint(actorID),
		CreatedByName: strings.TrimSpace(actorName),
	}
	if err := s.jobStore.CreateJob(&job); err != nil {
		return nil, err
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return &countryCodeRenameResponse{
		JobID:          job.ID,
		Status:         job.Status,
		CountryID:      countryID,
		OldCountryCode: oldCountryCode,
		NewCountryCode: req.CountryCode,
	}, nil
}

func (s *Service) HandleCountryCodeRenameJob(ctx context.Context, job *model.AdminJob) (string, error) {
	if job == nil {
		return "", errors.New("job is required")
	}
	if s.userDB == nil {
		return "", errors.New("user db is not configured")
	}
	if s.jobStore == nil {
		return "", errors.New("job store is not configured")
	}
	if s.roomClient == nil {
		return "", errors.New("room service client is not configured")
	}
	var payload countryCodeRenameJobPayload
	if err := json.Unmarshal([]byte(job.PayloadJSON), &payload); err != nil {
		return "", err
	}
	payload = normalizeCountryCodeRenamePayload(payload)
	if !validCountryCodeRenamePayload(payload) {
		return "", errCountryInvalid
	}

	ctx = appctx.WithContext(ctx, payload.AppCode)
	userCounts, err := s.renameCountryCodeInUserDB(ctx, payload)
	if err != nil {
		return "", err
	}
	nowMS := time.Now().UTC().UnixMilli()
	adminCounts, err := s.jobStore.RenameCountryCodeAdminReferences(payload.AppCode, payload.OldCountryCode, payload.NewCountryCode, nowMS)
	if err != nil {
		return "", err
	}
	roomCounts, err := s.roomClient.RenameOwnerCountryCode(ctx, roomclient.RenameOwnerCountryCodeRequest{
		RequestID:      firstNonEmptyString(payload.RequestID, fmt.Sprintf("country-code-rename-job-%d", job.ID)),
		OldCountryCode: payload.OldCountryCode,
		NewCountryCode: payload.NewCountryCode,
		AdminID:        uint64(payload.ActorID),
	})
	if err != nil {
		return "", err
	}
	result := countryCodeRenameResult{
		CountryID:       payload.CountryID,
		OldCountryCode:  payload.OldCountryCode,
		NewCountryCode:  payload.NewCountryCode,
		Users:           userCounts.Users,
		RegionMappings:  userCounts.RegionMappings,
		RebuildTasks:    userCounts.RebuildTasks,
		Banners:         adminCounts.Banners,
		SplashScreens:   adminCounts.SplashScreens,
		RoomListEntries: roomCounts.RoomListEntries,
		RoomSnapshots:   roomCounts.RoomSnapshots,
		CompletedAtMs:   time.Now().UTC().UnixMilli(),
	}
	body, err := json.Marshal(result)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func (s *Service) renameCountryCodeInUserDB(ctx context.Context, payload countryCodeRenameJobPayload) (countryCodeRenameUserCounts, error) {
	tx, err := s.userDB.BeginTx(ctx, nil)
	if err != nil {
		return countryCodeRenameUserCounts{}, err
	}
	defer tx.Rollback()

	country, err := queryCountryForUpdate(ctx, tx, payload.CountryID)
	if err != nil {
		return countryCodeRenameUserCounts{}, err
	}
	currentCode := strings.ToUpper(strings.TrimSpace(country.CountryCode))
	if currentCode != payload.OldCountryCode && currentCode != payload.NewCountryCode {
		return countryCodeRenameUserCounts{}, errCountryConflict
	}
	if err := ensureCountryCodeAvailable(ctx, tx, payload.CountryID, payload.NewCountryCode); err != nil {
		return countryCodeRenameUserCounts{}, err
	}

	nowMS := time.Now().UTC().UnixMilli()
	if _, err := tx.ExecContext(ctx, `
		UPDATE countries
		SET country_code = ?, country_name = ?, country_display_name = ?,
		    iso_alpha3 = ?, iso_numeric = ?, phone_country_code = ?, flag = ?, sort_order = ?,
		    updated_by_user_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND country_id = ?
	`, payload.NewCountryCode, payload.CountryName, payload.CountryDisplayName, nullableString(payload.ISOAlpha3), nullableString(payload.ISONumeric), nullableString(payload.PhoneCountryCode), payload.Flag, payload.SortOrder, nullableInt64(payload.ActorID), nowMS, payload.AppCode, payload.CountryID); err != nil {
		return countryCodeRenameUserCounts{}, mapCountryWriteError(err)
	}

	regionResult, err := tx.ExecContext(ctx, `
		UPDATE region_countries
		SET country_code = ?, updated_by_user_id = ?, updated_at_ms = ?
		WHERE app_code = ? AND country_code = ?
	`, payload.NewCountryCode, nullableInt64(payload.ActorID), nowMS, payload.AppCode, payload.OldCountryCode)
	if err != nil {
		return countryCodeRenameUserCounts{}, err
	}
	userResult, err := tx.ExecContext(ctx, `
		UPDATE users
		SET country = ?, updated_at_ms = ?
		WHERE app_code = ? AND country = ?
	`, payload.NewCountryCode, nowMS, payload.AppCode, payload.OldCountryCode)
	if err != nil {
		return countryCodeRenameUserCounts{}, err
	}
	taskResult, err := tx.ExecContext(ctx, `
		UPDATE user_region_rebuild_tasks
		SET country_code = ?, updated_at_ms = ?
		WHERE app_code = ? AND country_code = ? AND status IN (?, ?)
	`, payload.NewCountryCode, nowMS, payload.AppCode, payload.OldCountryCode, regionRebuildTaskStatusPending, regionRebuildTaskStatusRunning)
	if err != nil {
		return countryCodeRenameUserCounts{}, err
	}
	if err := tx.Commit(); err != nil {
		return countryCodeRenameUserCounts{}, err
	}

	regionRows, _ := regionResult.RowsAffected()
	userRows, _ := userResult.RowsAffected()
	taskRows, _ := taskResult.RowsAffected()
	return countryCodeRenameUserCounts{Users: userRows, RegionMappings: regionRows, RebuildTasks: taskRows}, nil
}

func queryCountryForUpdate(ctx context.Context, tx *sql.Tx, countryID int64) (*userclientCountryForRename, error) {
	var country userclientCountryForRename
	if err := tx.QueryRowContext(ctx, `
		SELECT country_id, country_code
		FROM countries
		WHERE app_code = ? AND country_id = ?
		FOR UPDATE
	`, appctx.FromContext(ctx), countryID).Scan(&country.CountryID, &country.CountryCode); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, errCountryNotFound
		}
		return nil, err
	}
	return &country, nil
}

type userclientCountryForRename struct {
	CountryID   int64
	CountryCode string
}

func ensureCountryCodeAvailable(ctx context.Context, tx *sql.Tx, countryID int64, countryCode string) error {
	var existingID int64
	err := tx.QueryRowContext(ctx, `
		SELECT country_id
		FROM countries
		WHERE app_code = ? AND country_code = ? AND country_id <> ?
		LIMIT 1
		FOR UPDATE
	`, appctx.FromContext(ctx), countryCode, countryID).Scan(&existingID)
	if errors.Is(err, sql.ErrNoRows) {
		return nil
	}
	if err != nil {
		return err
	}
	return errCountryConflict
}

func (s *Service) ensureNoActiveCountryCodeRenameJob(ctx context.Context, appCode string, countryID int64, oldCountryCode string, newCountryCode string) error {
	jobs, err := s.jobStore.ListActiveJobsByType(CountryCodeRenameJobType)
	if err != nil {
		return err
	}
	for _, job := range jobs {
		var payload countryCodeRenameJobPayload
		if err := json.Unmarshal([]byte(job.PayloadJSON), &payload); err != nil {
			continue
		}
		payload = normalizeCountryCodeRenamePayload(payload)
		if payload.AppCode != appctx.Normalize(appCode) {
			continue
		}
		if payload.CountryID == countryID ||
			payload.OldCountryCode == oldCountryCode ||
			payload.OldCountryCode == newCountryCode ||
			payload.NewCountryCode == oldCountryCode ||
			payload.NewCountryCode == newCountryCode {
			return errCountryRenameJobConflict
		}
	}
	return nil
}

func normalizeRenameCountryCodeRequest(req renameCountryCodeRequest) renameCountryCodeRequest {
	req.CountryName = strings.TrimSpace(req.CountryName)
	req.CountryCode = strings.ToUpper(strings.TrimSpace(req.CountryCode))
	req.CountryDisplayName = strings.TrimSpace(req.CountryDisplayName)
	req.ISOAlpha3 = strings.ToUpper(strings.TrimSpace(req.ISOAlpha3))
	req.ISONumeric = strings.TrimSpace(req.ISONumeric)
	req.PhoneCountryCode = strings.TrimSpace(req.PhoneCountryCode)
	req.Flag = strings.TrimSpace(req.Flag)
	return req
}

func validCountryRenameRequest(req renameCountryCodeRequest) bool {
	return validCountryCreateRequest(createCountryRequest{
		CountryName:        req.CountryName,
		CountryCode:        req.CountryCode,
		CountryDisplayName: req.CountryDisplayName,
		SortOrder:          req.SortOrder,
		ISOAlpha3:          req.ISOAlpha3,
		ISONumeric:         req.ISONumeric,
		PhoneCountryCode:   req.PhoneCountryCode,
		Flag:               req.Flag,
	})
}

func normalizeCountryCodeRenamePayload(payload countryCodeRenameJobPayload) countryCodeRenameJobPayload {
	payload.AppCode = appctx.Normalize(payload.AppCode)
	payload.OldCountryCode = strings.ToUpper(strings.TrimSpace(payload.OldCountryCode))
	payload.NewCountryCode = strings.ToUpper(strings.TrimSpace(payload.NewCountryCode))
	payload.CountryName = strings.TrimSpace(payload.CountryName)
	payload.CountryDisplayName = strings.TrimSpace(payload.CountryDisplayName)
	payload.ISOAlpha3 = strings.ToUpper(strings.TrimSpace(payload.ISOAlpha3))
	payload.ISONumeric = strings.TrimSpace(payload.ISONumeric)
	payload.PhoneCountryCode = strings.TrimSpace(payload.PhoneCountryCode)
	payload.Flag = strings.TrimSpace(payload.Flag)
	payload.ActorName = strings.TrimSpace(payload.ActorName)
	payload.RequestID = strings.TrimSpace(payload.RequestID)
	return payload
}

func validCountryCodeRenamePayload(payload countryCodeRenameJobPayload) bool {
	return payload.CountryID > 0 &&
		payload.ActorID >= 0 &&
		payload.OldCountryCode != payload.NewCountryCode &&
		countryCodePattern.MatchString(payload.OldCountryCode) &&
		validCountryRenameRequest(renameCountryCodeRequest{
			CountryName:        payload.CountryName,
			CountryCode:        payload.NewCountryCode,
			CountryDisplayName: payload.CountryDisplayName,
			SortOrder:          payload.SortOrder,
			ISOAlpha3:          payload.ISOAlpha3,
			ISONumeric:         payload.ISONumeric,
			PhoneCountryCode:   payload.PhoneCountryCode,
			Flag:               payload.Flag,
		})
}

func firstNonEmptyString(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}
