package tools

import (
	"context"
	"database/sql"
	"fmt"
	"skraak/db"
	"skraak/utils"
	"strings"
)

// LocationInput defines the input parameters for the create_or_update_location tool
type LocationInput struct {
	ID          *string  `json:"id,omitempty"`
	DatasetID   *string  `json:"dataset_id,omitempty"`
	Name        *string  `json:"name,omitempty"`
	Latitude    *float64 `json:"latitude,omitempty"`
	Longitude   *float64 `json:"longitude,omitempty"`
	TimezoneID  *string  `json:"timezone_id,omitempty"`
	Description *string  `json:"description,omitempty"`
}

// LocationOutput defines the output structure
type LocationOutput struct {
	Location db.Location `json:"location"`
	Message  string      `json:"message"`
}

// CreateOrUpdateLocation creates a new location or updates an existing one with GPS coordinates
func CreateOrUpdateLocation(
	ctx context.Context,
	input LocationInput,
) (LocationOutput, error) {
	if input.ID != nil && strings.TrimSpace(*input.ID) != "" {
		return updateLocation(ctx, input)
	}
	return createLocation(ctx, input)
}

// validateLocationFields validates fields common to both create and update
func validateLocationFields(input LocationInput) error {
	if err := utils.ValidateOptionalStringLength(input.Name, "name", utils.MaxNameLen); err != nil {
		return err
	}
	if err := utils.ValidateOptionalStringLength(input.Description, "description", utils.MaxDescriptionLen); err != nil {
		return err
	}
	if input.Latitude != nil {
		if err := utils.ValidateRange(*input.Latitude, "latitude", -90.0, 90.0); err != nil {
			return err
		}
	}
	if input.Longitude != nil {
		if err := utils.ValidateRange(*input.Longitude, "longitude", -180.0, 180.0); err != nil {
			return err
		}
	}
	if input.TimezoneID != nil {
		if err := utils.ValidateStringLength(*input.TimezoneID, "timezone_id", utils.MaxTimezoneLen); err != nil {
			return err
		}
		if err := utils.ValidateTimezone(*input.TimezoneID); err != nil {
			return err
		}
	}
	return nil
}

// validateCreateFields validates required fields for creating a location.
func validateCreateFields(input LocationInput) error {
	if input.DatasetID == nil || strings.TrimSpace(*input.DatasetID) == "" {
		return fmt.Errorf("dataset_id is required when creating a location")
	}
	if input.Name == nil || strings.TrimSpace(*input.Name) == "" {
		return fmt.Errorf("name is required when creating a location")
	}
	if input.Latitude == nil {
		return fmt.Errorf("latitude is required when creating a location")
	}
	if input.Longitude == nil {
		return fmt.Errorf("longitude is required when creating a location")
	}
	if input.TimezoneID == nil || strings.TrimSpace(*input.TimezoneID) == "" {
		return fmt.Errorf("timezone_id is required when creating a location")
	}
	if err := utils.ValidateShortID(*input.DatasetID, "dataset_id"); err != nil {
		return err
	}
	return validateLocationFields(input)
}

// fetchLocationByID scans a full location row from a query that selects
// id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id.
func fetchLocationByID(ctx context.Context, queryer interface {
	QueryRowContext(context.Context, string, ...any) *sql.Row
}, id string) (db.Location, error) {
	const selectCols = "SELECT id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id FROM location WHERE id = ?"
	var loc db.Location
	err := queryer.QueryRowContext(ctx, selectCols, id).Scan(
		&loc.ID, &loc.DatasetID, &loc.Name, &loc.Latitude, &loc.Longitude,
		&loc.Description, &loc.CreatedAt, &loc.LastModified, &loc.Active, &loc.TimezoneID)
	return loc, err
}

func createLocation(ctx context.Context, input LocationInput) (LocationOutput, error) {
	var output LocationOutput

	if err := validateCreateFields(input); err != nil {
		return output, err
	}

	err := db.WithWriteTx(ctx, dbPath, "create_or_update_location", func(database *sql.DB, tx *db.LoggedTx) error {
		if err := verifyDatasetExistsAndActive(ctx, tx, *input.DatasetID); err != nil {
			return err
		}

		// Check for existing location with same name in dataset (UNIQUE constraint)
		var existingID string
		qerr := tx.QueryRowContext(ctx,
			"SELECT id FROM location WHERE dataset_id = ? AND name = ? AND active = true",
			*input.DatasetID, *input.Name,
		).Scan(&existingID)

		if qerr == nil {
			result, rerr := returnExistingLocation(ctx, tx, existingID)
			if rerr != nil {
				return rerr
			}
			output = result
			return nil
		}

		// Generate ID
		id, gerr := utils.GenerateShortID()
		if gerr != nil {
			return fmt.Errorf("failed to generate ID: %w", gerr)
		}

		// Insert location
		if _, err := tx.ExecContext(ctx,
			"INSERT INTO location (id, dataset_id, name, latitude, longitude, timezone_id, description, created_at, last_modified, active) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, TRUE)",
			id, *input.DatasetID, *input.Name, *input.Latitude, *input.Longitude, *input.TimezoneID, input.Description,
		); err != nil {
			return fmt.Errorf("failed to create location: %w", err)
		}

		// Fetch the created location
		location, ferr := fetchLocationByID(ctx, tx, id)
		if ferr != nil {
			return fmt.Errorf("failed to fetch created location: %w", ferr)
		}

		output.Location = location
		output.Message = fmt.Sprintf("Successfully created location '%s' with ID %s (%.6f, %.6f, %s)",
			location.Name, location.ID, location.Latitude, location.Longitude, location.TimezoneID)
		return nil
	})
	return output, err
}

// returnExistingLocation handles the case where a location already exists in the dataset.
// Caller is responsible for committing the transaction.
func returnExistingLocation(ctx context.Context, tx *db.LoggedTx, existingID string) (LocationOutput, error) {
	var output LocationOutput
	location, err := fetchLocationByID(ctx, tx, existingID)
	if err != nil {
		return output, fmt.Errorf("failed to fetch existing location: %w", err)
	}
	output.Location = location
	output.Message = fmt.Sprintf("Location '%s' already exists in dataset (ID: %s) - returning existing location", location.Name, location.ID)
	return output, nil
}

// verifyDatasetExistsAndActive checks that a dataset exists and is active.
func verifyDatasetExistsAndActive(ctx context.Context, q db.Querier, datasetID string) error {
	_, err := db.DatasetExistsAndActive(q, datasetID)
	return err
}

func updateLocation(ctx context.Context, input LocationInput) (LocationOutput, error) {
	var output LocationOutput
	locationID := *input.ID

	// Validate ID format
	if err := utils.ValidateShortID(locationID, "location_id"); err != nil {
		return output, err
	}

	if err := validateLocationFields(input); err != nil {
		return output, err
	}

	// Validate dataset_id format if provided
	if err := utils.ValidateOptionalShortID(input.DatasetID, "dataset_id"); err != nil {
		return output, err
	}

	err := db.WithWriteTx(ctx, dbPath, "create_or_update_location", func(database *sql.DB, tx *db.LoggedTx) error {
		if err := verifyLocationExistsAndActive(database, locationID); err != nil {
			return err
		}

		// Verify dataset exists if DatasetID provided (relationship consistency)
		if input.DatasetID != nil {
			if err := verifyDatasetExistsAndActive(ctx, database, *input.DatasetID); err != nil {
				return err
			}
		}

		updates, args, uerr := buildLocationUpdates(input, locationID)
		if uerr != nil {
			return uerr
		}
		query := fmt.Sprintf("UPDATE location SET %s WHERE id = ?", strings.Join(updates, ", "))

		if _, err := tx.ExecContext(ctx, query, args...); err != nil {
			return fmt.Errorf("failed to update location: %w", err)
		}

		// Fetch the updated location
		location, ferr := fetchLocationByID(ctx, tx, locationID)
		if ferr != nil {
			return fmt.Errorf("failed to fetch updated location: %w", ferr)
		}

		output.Location = location
		output.Message = fmt.Sprintf("Successfully updated location '%s' (ID: %s)", location.Name, location.ID)
		return nil
	})
	return output, err
}

// verifyLocationExistsAndActive checks that a location exists and is active.
func verifyLocationExistsAndActive(queryer interface {
	QueryRow(string, ...any) *sql.Row
}, locationID string) error {
	var exists, active bool
	err := queryer.QueryRow(
		"SELECT EXISTS(SELECT 1 FROM location WHERE id = ?), COALESCE((SELECT active FROM location WHERE id = ?), false)",
		locationID, locationID,
	).Scan(&exists, &active)
	if err != nil {
		return fmt.Errorf("failed to query location: %w", err)
	}
	if !exists {
		return fmt.Errorf("location not found: %s", locationID)
	}
	if !active {
		return fmt.Errorf("location '%s' is not active (cannot update inactive locations)", locationID)
	}
	return nil
}

// buildLocationUpdates builds the dynamic SET clauses and args for an UPDATE.
func buildLocationUpdates(input LocationInput, locationID string) ([]string, []any, error) {
	updates := []string{}
	args := []any{}

	if input.DatasetID != nil {
		updates = append(updates, "dataset_id = ?")
		args = append(args, *input.DatasetID)
	}
	if input.Name != nil {
		updates = append(updates, "name = ?")
		args = append(args, *input.Name)
	}
	if input.Latitude != nil {
		updates = append(updates, "latitude = ?")
		args = append(args, *input.Latitude)
	}
	if input.Longitude != nil {
		updates = append(updates, "longitude = ?")
		args = append(args, *input.Longitude)
	}
	if input.Description != nil {
		updates = append(updates, "description = ?")
		args = append(args, *input.Description)
	}
	if input.TimezoneID != nil {
		updates = append(updates, "timezone_id = ?")
		args = append(args, *input.TimezoneID)
	}

	if len(updates) == 0 {
		return nil, nil, fmt.Errorf("no fields provided to update")
	}

	updates = append(updates, "last_modified = now()")
	args = append(args, locationID)
	return updates, args, nil
}