package geo

import (
	"context"
	"database/sql"
	"errors"
	"net"
	"time"

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

const defaultLookupQuery = `
SELECT
  ip::text,
  country,
  region,
  city,
  latitude,
  longitude
FROM geoip.lookup_city($1);
`

type postgresResolver struct {
	db          *sql.DB
	lookupQuery string
}

func newPostgresResolver(databaseURL, lookupQuery string) (Resolver, error) {
	if databaseURL == "" {
		return nil, errors.New("database url is required for postgres backend")
	}

	db, err := sql.Open("pgx", databaseURL)
	if err != nil {
		return nil, err
	}

	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(2)
	db.SetConnMaxIdleTime(5 * time.Minute)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		_ = db.Close()
		return nil, err
	}

	if lookupQuery == "" {
		lookupQuery = defaultLookupQuery
	}

	return &postgresResolver{
		db:          db,
		lookupQuery: lookupQuery,
	}, nil
}

func (r *postgresResolver) Close() error {
	return r.db.Close()
}

func (r *postgresResolver) Lookup(ipStr string) (Location, error) {
	ip := net.ParseIP(ipStr)
	if ip == nil {
		return Location{}, ErrInvalidIP
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	row := r.db.QueryRowContext(ctx, r.lookupQuery, ip.String())

	var (
		resolvedIP          string
		country, region     sql.NullString
		city                sql.NullString
		latitude, longitude sql.NullFloat64
	)

	if err := row.Scan(&resolvedIP, &country, &region, &city, &latitude, &longitude); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return Location{}, ErrNotFound
		}
		return Location{}, err
	}

	return Location{
		IP:        resolvedIP,
		Country:   country.String,
		Region:    region.String,
		City:      city.String,
		Address:   buildAddress(city.String, region.String, country.String),
		Latitude:  latitude.Float64,
		Longitude: longitude.Float64,
	}, nil
}
