package postgres

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/jackc/pgx/v5"
)

type OpsUser struct {
	ID          string
	PublicID    string
	CountryCode string
	Mobile      string
	DisplayName string
	Status      string
	LastLoginAt *time.Time
}

type OpsRole struct {
	ID          string
	RoleCode    string
	DisplayName string
	RoleRank    int
}

type CreateOpsUserParams struct {
	PublicID    string
	CountryCode string
	Mobile      string
	DisplayName string
	Status      string
}

type CreateOpsRefreshTokenParams struct {
	OpsUserID  string
	DeviceKey  string
	TokenHash  string
	ExpiresAt  time.Time
}

type OpsRefreshTokenRecord struct {
	ID        string
	OpsUserID string
	DeviceKey *string
	ExpiresAt time.Time
	IsRevoked bool
}

func (s *Store) CountOpsUsers(ctx context.Context) (int, error) {
	row := s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM ops_users WHERE status <> 'deleted'`)
	var count int
	if err := row.Scan(&count); err != nil {
		return 0, fmt.Errorf("count ops users: %w", err)
	}
	return count, nil
}

func (s *Store) GetOpsUserByMobile(ctx context.Context, db queryRower, countryCode, mobile string) (*OpsUser, error) {
	row := db.QueryRow(ctx, `
		SELECT id, ops_user_public_id, country_code, mobile, display_name, status, last_login_at
		FROM ops_users
		WHERE country_code = $1 AND mobile = $2
		LIMIT 1
	`, countryCode, mobile)
	return scanOpsUser(row)
}

func (s *Store) GetOpsUserByID(ctx context.Context, db queryRower, opsUserID string) (*OpsUser, error) {
	row := db.QueryRow(ctx, `
		SELECT id, ops_user_public_id, country_code, mobile, display_name, status, last_login_at
		FROM ops_users
		WHERE id = $1
	`, opsUserID)
	return scanOpsUser(row)
}

func (s *Store) CreateOpsUser(ctx context.Context, tx Tx, params CreateOpsUserParams) (*OpsUser, error) {
	row := tx.QueryRow(ctx, `
		INSERT INTO ops_users (ops_user_public_id, country_code, mobile, display_name, status)
		VALUES ($1, $2, $3, $4, $5)
		RETURNING id, ops_user_public_id, country_code, mobile, display_name, status, last_login_at
	`, params.PublicID, params.CountryCode, params.Mobile, params.DisplayName, params.Status)
	return scanOpsUser(row)
}

func (s *Store) TouchOpsUserLogin(ctx context.Context, tx Tx, opsUserID string) error {
	_, err := tx.Exec(ctx, `
		UPDATE ops_users
		SET last_login_at = NOW()
		WHERE id = $1
	`, opsUserID)
	if err != nil {
		return fmt.Errorf("touch ops user last login: %w", err)
	}
	return nil
}

func (s *Store) GetOpsRoleByCode(ctx context.Context, tx Tx, roleCode string) (*OpsRole, error) {
	row := tx.QueryRow(ctx, `
		SELECT id, role_code, display_name, role_rank
		FROM ops_roles
		WHERE role_code = $1
		  AND status = 'active'
		LIMIT 1
	`, roleCode)
	return scanOpsRole(row)
}

func (s *Store) AssignOpsRole(ctx context.Context, tx Tx, opsUserID, roleID string) error {
	_, err := tx.Exec(ctx, `
		INSERT INTO ops_user_roles (ops_user_id, ops_role_id, status)
		VALUES ($1, $2, 'active')
		ON CONFLICT (ops_user_id, ops_role_id) DO NOTHING
	`, opsUserID, roleID)
	if err != nil {
		return fmt.Errorf("assign ops role: %w", err)
	}
	return nil
}

func (s *Store) GetPrimaryOpsRole(ctx context.Context, db queryRower, opsUserID string) (*OpsRole, error) {
	row := db.QueryRow(ctx, `
		SELECT r.id, r.role_code, r.display_name, r.role_rank
		FROM ops_user_roles ur
		JOIN ops_roles r ON r.id = ur.ops_role_id
		WHERE ur.ops_user_id = $1
		  AND ur.status = 'active'
		  AND r.status = 'active'
		ORDER BY r.role_rank DESC, r.created_at ASC
		LIMIT 1
	`, opsUserID)
	return scanOpsRole(row)
}

func (s *Store) CreateOpsRefreshToken(ctx context.Context, tx Tx, params CreateOpsRefreshTokenParams) (string, error) {
	row := tx.QueryRow(ctx, `
		INSERT INTO ops_refresh_tokens (ops_user_id, device_key, token_hash, expires_at)
		VALUES ($1, NULLIF($2, ''), $3, $4)
		RETURNING id
	`, params.OpsUserID, params.DeviceKey, params.TokenHash, params.ExpiresAt)

	var id string
	if err := row.Scan(&id); err != nil {
		return "", fmt.Errorf("create ops refresh token: %w", err)
	}
	return id, nil
}

func (s *Store) GetOpsRefreshTokenForUpdate(ctx context.Context, tx Tx, tokenHash string) (*OpsRefreshTokenRecord, error) {
	row := tx.QueryRow(ctx, `
		SELECT id, ops_user_id, device_key, expires_at, revoked_at IS NOT NULL
		FROM ops_refresh_tokens
		WHERE token_hash = $1
		FOR UPDATE
	`, tokenHash)

	var record OpsRefreshTokenRecord
	err := row.Scan(&record.ID, &record.OpsUserID, &record.DeviceKey, &record.ExpiresAt, &record.IsRevoked)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("query ops refresh token for update: %w", err)
	}
	return &record, nil
}

func (s *Store) RotateOpsRefreshToken(ctx context.Context, tx Tx, oldTokenID, newTokenID string) error {
	_, err := tx.Exec(ctx, `
		UPDATE ops_refresh_tokens
		SET revoked_at = NOW(), replaced_by_token_id = $2
		WHERE id = $1
	`, oldTokenID, newTokenID)
	if err != nil {
		return fmt.Errorf("rotate ops refresh token: %w", err)
	}
	return nil
}

func (s *Store) RevokeOpsRefreshToken(ctx context.Context, tokenHash string) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE ops_refresh_tokens
		SET revoked_at = COALESCE(revoked_at, NOW())
		WHERE token_hash = $1
	`, tokenHash)
	if err != nil {
		return fmt.Errorf("revoke ops refresh token: %w", err)
	}
	return nil
}

func scanOpsUser(row pgx.Row) (*OpsUser, error) {
	var item OpsUser
	err := row.Scan(&item.ID, &item.PublicID, &item.CountryCode, &item.Mobile, &item.DisplayName, &item.Status, &item.LastLoginAt)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("scan ops user: %w", err)
	}
	return &item, nil
}

func scanOpsRole(row pgx.Row) (*OpsRole, error) {
	var item OpsRole
	err := row.Scan(&item.ID, &item.RoleCode, &item.DisplayName, &item.RoleRank)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("scan ops role: %w", err)
	}
	return &item, nil
}
