package repository

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"

	"erp-mvp/core-service/internal/models"

	"github.com/google/uuid"
)

type OperationsRepository interface {
	PlaceItem(ctx context.Context, placement *models.ItemPlacement) error
	MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error
	GetByItem(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error)
	GetByLocation(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error)
	GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.ItemPlacement, error)
	UpdateQuantity(ctx context.Context, id uuid.UUID, quantity int, orgID uuid.UUID) error
	Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
	Search(ctx context.Context, orgID uuid.UUID, query string, category string, address string) ([]*models.ItemWithLocation, error)
}

type operationsRepository struct {
	db *sql.DB
}

func NewOperationsRepository(db *sql.DB) OperationsRepository {
	return &operationsRepository{db: db}
}

func (r *operationsRepository) PlaceItem(ctx context.Context, placement *models.ItemPlacement) error {
	query := `
		INSERT INTO item_placements (id, organization_id, item_id, location_id, quantity, created_at)
		VALUES ($1, $2, $3, $4, $5, $6)
	`

	_, err := r.db.ExecContext(ctx, query,
		placement.ID,
		placement.OrganizationID,
		placement.ItemID,
		placement.LocationID,
		placement.Quantity,
		placement.CreatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to place item: %w", err)
	}

	return nil
}

func (r *operationsRepository) MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error {
	query := `
		UPDATE item_placements
		SET location_id = $2
		WHERE id = $1 AND organization_id = $3
	`

	result, err := r.db.ExecContext(ctx, query, placementID, newLocationID, orgID)
	if err != nil {
		return fmt.Errorf("failed to move item: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("item placement not found")
	}

	return nil
}

func (r *operationsRepository) GetByItem(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) {
	query := `
		SELECT id, organization_id, item_id, location_id, quantity, created_at
		FROM item_placements
		WHERE item_id = $1 AND organization_id = $2
		ORDER BY created_at DESC
	`

	rows, err := r.db.QueryContext(ctx, query, itemID, orgID)
	if err != nil {
		return nil, fmt.Errorf("failed to query item placements: %w", err)
	}
	defer rows.Close()

	var placements []*models.ItemPlacement
	for rows.Next() {
		placement := &models.ItemPlacement{}
		err := rows.Scan(
			&placement.ID,
			&placement.OrganizationID,
			&placement.ItemID,
			&placement.LocationID,
			&placement.Quantity,
			&placement.CreatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan item placement: %w", err)
		}

		placements = append(placements, placement)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating item placements: %w", err)
	}

	return placements, nil
}

func (r *operationsRepository) GetByLocation(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) {
	query := `
		SELECT id, organization_id, item_id, location_id, quantity, created_at
		FROM item_placements
		WHERE location_id = $1 AND organization_id = $2
		ORDER BY created_at DESC
	`

	rows, err := r.db.QueryContext(ctx, query, locationID, orgID)
	if err != nil {
		return nil, fmt.Errorf("failed to query location placements: %w", err)
	}
	defer rows.Close()

	var placements []*models.ItemPlacement
	for rows.Next() {
		placement := &models.ItemPlacement{}
		err := rows.Scan(
			&placement.ID,
			&placement.OrganizationID,
			&placement.ItemID,
			&placement.LocationID,
			&placement.Quantity,
			&placement.CreatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan item placement: %w", err)
		}

		placements = append(placements, placement)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating location placements: %w", err)
	}

	return placements, nil
}

func (r *operationsRepository) GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.ItemPlacement, error) {
	query := `
		SELECT id, organization_id, item_id, location_id, quantity, created_at
		FROM item_placements
		WHERE id = $1 AND organization_id = $2
	`

	placement := &models.ItemPlacement{}
	err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
		&placement.ID,
		&placement.OrganizationID,
		&placement.ItemID,
		&placement.LocationID,
		&placement.Quantity,
		&placement.CreatedAt,
	)

	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("item placement not found")
		}
		return nil, fmt.Errorf("failed to get item placement: %w", err)
	}

	return placement, nil
}

func (r *operationsRepository) UpdateQuantity(ctx context.Context, id uuid.UUID, quantity int, orgID uuid.UUID) error {
	query := `
		UPDATE item_placements
		SET quantity = $2
		WHERE id = $1 AND organization_id = $3
	`

	result, err := r.db.ExecContext(ctx, query, id, quantity, orgID)
	if err != nil {
		return fmt.Errorf("failed to update quantity: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("item placement not found")
	}

	return nil
}

func (r *operationsRepository) Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error {
	query := `
		DELETE FROM item_placements
		WHERE id = $1 AND organization_id = $2
	`

	result, err := r.db.ExecContext(ctx, query, id, orgID)
	if err != nil {
		return fmt.Errorf("failed to delete item placement: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("item placement not found")
	}

	return nil
}

func (r *operationsRepository) Search(ctx context.Context, orgID uuid.UUID, query string, category string, address string) ([]*models.ItemWithLocation, error) {
	baseQuery := `
		SELECT 
			i.id, i.organization_id, i.name, i.description, i.category, i.created_at,
			sl.id, sl.organization_id, sl.parent_id, sl.name, sl.address, sl.type, sl.coordinates, sl.created_at,
			ip.quantity
		FROM items i
		JOIN item_placements ip ON i.id = ip.item_id
		JOIN storage_locations sl ON ip.location_id = sl.id
		WHERE i.organization_id = $1 AND sl.organization_id = $1
	`

	var args []interface{}
	args = append(args, orgID)
	argIndex := 2

	if query != "" {
		baseQuery += fmt.Sprintf(" AND (i.name ILIKE $%d OR i.description ILIKE $%d)", argIndex, argIndex)
		args = append(args, "%"+query+"%")
		argIndex++
	}

	if category != "" {
		baseQuery += fmt.Sprintf(" AND i.category = $%d", argIndex)
		args = append(args, category)
		argIndex++
	}

	if address != "" {
		baseQuery += fmt.Sprintf(" AND sl.address ILIKE $%d", argIndex)
		args = append(args, "%"+address+"%")
		argIndex++
	}

	baseQuery += " ORDER BY i.name, sl.name"

	rows, err := r.db.QueryContext(ctx, baseQuery, args...)
	if err != nil {
		return nil, fmt.Errorf("failed to search items with locations: %w", err)
	}
	defer rows.Close()

	var results []*models.ItemWithLocation
	for rows.Next() {
		var coordinatesJSON []byte
		itemWithLocation := &models.ItemWithLocation{}

		err := rows.Scan(
			&itemWithLocation.Item.ID,
			&itemWithLocation.Item.OrganizationID,
			&itemWithLocation.Item.Name,
			&itemWithLocation.Item.Description,
			&itemWithLocation.Item.Category,
			&itemWithLocation.Item.CreatedAt,
			&itemWithLocation.Location.ID,
			&itemWithLocation.Location.OrganizationID,
			&itemWithLocation.Location.ParentID,
			&itemWithLocation.Location.Name,
			&itemWithLocation.Location.Address,
			&itemWithLocation.Location.Type,
			&coordinatesJSON,
			&itemWithLocation.Location.CreatedAt,
			&itemWithLocation.Quantity,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan item with location: %w", err)
		}

		// Конвертируем JSON строку в map
		if len(coordinatesJSON) > 0 {
			err = json.Unmarshal(coordinatesJSON, &itemWithLocation.Location.Coordinates)
			if err != nil {
				return nil, fmt.Errorf("failed to unmarshal coordinates: %w", err)
			}
		} else {
			itemWithLocation.Location.Coordinates = make(models.JSON)
		}

		results = append(results, itemWithLocation)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating search results: %w", err)
	}

	return results, nil
}
