package postgres

import (
	"context"
	"database/sql"

	"github.com/rede5/gohorsejobs/backend/internal/core/domain/entity"
)

type LocationRepository struct {
	db *sql.DB
}

func NewLocationRepository(db *sql.DB) *LocationRepository {
	return &LocationRepository{db: db}
}

func (r *LocationRepository) ListCountries(ctx context.Context) ([]*entity.Country, error) {
	query := `SELECT id, name, iso2, iso3, phonecode, currency, emoji, emoji_u, created_at, updated_at FROM countries ORDER BY name ASC`
	rows, err := r.db.QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	countries := []*entity.Country{}
	for rows.Next() {
		c := &entity.Country{}
		// Scan columns. Be careful with NULL handling if fields are nullable, but schema says NOT NULL mostly.
		// Check schema again. phonecode, etc can be null.
		// For simplicity, we scan into sql.NullString or just string (and risk error if NULL).
		// Migration 021 says:
		// name VARCHAR(100) NOT NULL
		// iso2 CHAR(2)
		// phonecode VARCHAR(255)
		// ...
		// So nullable fields need handling.

		var iso2, iso3, phonecode, currency, emoji, emojiU sql.NullString

		if err := rows.Scan(&c.ID, &c.Name, &iso2, &iso3, &phonecode, &currency, &emoji, &emojiU, &c.CreatedAt, &c.UpdatedAt); err != nil {
			return nil, err
		}
		c.ISO2 = iso2.String
		c.ISO3 = iso3.String
		c.PhoneCode = phonecode.String
		c.Currency = currency.String
		c.Emoji = emoji.String
		c.EmojiU = emojiU.String

		countries = append(countries, c)
	}
	return countries, nil
}

func (r *LocationRepository) ListStates(ctx context.Context, countryID int64) ([]*entity.State, error) {
	query := `SELECT id, name, country_id, country_code, iso2, type, latitude, longitude FROM states WHERE country_id = $1 ORDER BY name ASC`
	rows, err := r.db.QueryContext(ctx, query, countryID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	states := []*entity.State{}
	for rows.Next() {
		s := &entity.State{}
		var iso2, typeStr sql.NullString
		var lat, lng sql.NullFloat64

		if err := rows.Scan(&s.ID, &s.Name, &s.CountryID, &s.CountryCode, &iso2, &typeStr, &lat, &lng); err != nil {
			return nil, err
		}
		s.ISO2 = iso2.String
		s.Type = typeStr.String
		s.Latitude = lat.Float64
		s.Longitude = lng.Float64

		states = append(states, s)
	}
	return states, nil
}

func (r *LocationRepository) ListCities(ctx context.Context, stateID int64) ([]*entity.City, error) {
	query := `SELECT id, name, state_id, country_id, latitude, longitude FROM cities WHERE state_id = $1 ORDER BY name ASC`
	rows, err := r.db.QueryContext(ctx, query, stateID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	cities := []*entity.City{}
	for rows.Next() {
		c := &entity.City{}
		// schema: latitude NOT NULL, longitude NOT NULL.
		if err := rows.Scan(&c.ID, &c.Name, &c.StateID, &c.CountryID, &c.Latitude, &c.Longitude); err != nil {
			return nil, err
		}
		cities = append(cities, c)
	}
	return cities, nil
}

func (r *LocationRepository) Search(ctx context.Context, query string, countryID int64) ([]*entity.LocationSearchResult, error) {
	// Search Cities and States
	// Simple LIKE query
	q := "%" + query + "%"

	sqlQuery := `
		SELECT id, name, 'state' as type, country_id, NULL as state_id, '' as region_name
		FROM states
		WHERE country_id = $1 AND name ILIKE $2
		UNION ALL
		SELECT c.id, c.name, 'city' as type, c.country_id, c.state_id, s.name as region_name
		FROM cities c
		JOIN states s ON c.state_id = s.id
		WHERE c.country_id = $1 AND c.name ILIKE $2
		LIMIT 20
	`

	rows, err := r.db.QueryContext(ctx, sqlQuery, countryID, q)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	results := []*entity.LocationSearchResult{}
	for rows.Next() {
		res := &entity.LocationSearchResult{}
		var stateID sql.NullInt64
		var regionName sql.NullString

		if err := rows.Scan(&res.ID, &res.Name, &res.Type, &res.CountryID, &stateID, &regionName); err != nil {
			return nil, err
		}

		if stateID.Valid {
			id := stateID.Int64
			res.StateID = &id
		}
		res.RegionName = regionName.String
		results = append(results, res)
	}
	return results, nil
}
