package ru.kovbasa.pages;

import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Mouse;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.PlaywrightException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ru.kovbasa.playwright.TestConfig;

public class CatalogPage extends BasePage {

    private static final Pattern DURATION_PATTERN = Pattern.compile("(\\d+)\\s+месяц");

    public CatalogPage(Page page, TestConfig config) {
        super(page, config);
    }

    public void open() {
        openPath("/catalog/courses");
    }

    public boolean isDefaultDirectionSelected() {
        return isOptionChecked("Все направления");
    }

    public boolean isDefaultLevelSelected() {
        return isOptionChecked("Любой уровень сложности")
                || isOptionChecked("Любой уровень");
    }

    public void setDurationRange(int minMonths, int maxMonths) {
        Locator durationSection = getFilterSection("Продолжительность");

        Locator rangeLabel = durationSection.locator("label").filter(
                new Locator.FilterOptions().setHasText(String.valueOf(minMonths))
        ).filter(new Locator.FilterOptions().setHasText(String.valueOf(maxMonths))).first();
        if (rangeLabel.count() > 0) {
            rangeLabel.click();
            waitForDurationFilterApplied(minMonths, maxMonths);
            return;
        }

        Locator rangeText = durationSection.locator(
                "text=/\\b" + minMonths + "\\b.*\\b" + maxMonths + "\\b/").first();
        if (rangeText.count() > 0) {
            rangeText.click();
            waitForDurationFilterApplied(minMonths, maxMonths);
            return;
        }

        Locator sliders = page.locator("[role='slider']");
        if (sliders.count() >= 2) {
            Locator minSlider = sliders.nth(0);
            Locator maxSlider = sliders.nth(1);

            String minBefore = minSlider.getAttribute("aria-valuenow");
            String maxBefore = maxSlider.getAttribute("aria-valuenow");

            dragSliderTo(minSlider, minMonths);
            dragSliderTo(maxSlider, maxMonths);

            String minAfter = minSlider.getAttribute("aria-valuenow");
            String maxAfter = maxSlider.getAttribute("aria-valuenow");

            if (minBefore != null && minBefore.equals(minAfter)) {
                nudgeSliderWithKeyboard(minSlider, minMonths);
            }
            if (maxBefore != null && maxBefore.equals(maxAfter)) {
                nudgeSliderWithKeyboard(maxSlider, maxMonths);
            }
            nudgeSliderWithKeyboard(minSlider, minMonths);
            nudgeSliderWithKeyboard(maxSlider, maxMonths);
            waitForSliderValues(minSlider, maxSlider, minMonths, maxMonths);
            waitForDurationFilterApplied(minMonths, maxMonths);
            return;
        }

        Locator inputs = durationSection.locator("input");
        if (inputs.count() >= 2) {
            String type = inputs.nth(0).getAttribute("type");
            if (type != null && (type.equals("number") || type.equals("text"))) {
                inputs.nth(0).fill(String.valueOf(minMonths));
                inputs.nth(1).fill(String.valueOf(maxMonths));
                inputs.nth(1).press("Enter");
                return;
            }
        }

        Locator rangeInputs = durationSection.locator("input[type='range']");
        if (rangeInputs.count() >= 2) {
            rangeInputs.nth(0).fill(String.valueOf(minMonths));
            rangeInputs.nth(1).fill(String.valueOf(maxMonths));
            return;
        }

        // no suitable controls found; rely on current state
    }

    public void selectDirection(String direction) {
        Locator option = page.getByText(direction).first();
        try {
            option.click(new Locator.ClickOptions().setForce(true));
        } catch (PlaywrightException ex) {
            option.evaluate("el => el.click()");
        }
    }

    public boolean isDirectionSelected(String direction) {
        return isOptionChecked(direction);
    }

    public String getFirstCourseTitle() {
        Locator card = getCourseCards().first();
        card.waitFor();
        Locator title = card.locator("h4, h5, h6").first();
        String text = title.textContent();
        return text == null ? "" : text.trim();
    }

    public List<String> getCourseTitles(int limit) {
        Locator cards = getCourseCards();
        Locator titles = cards.locator("h4, h5, h6");
        titles.first().waitFor();
        List<String> all = titles.allTextContents();
        return all.size() > limit ? all.subList(0, limit) : all;
    }

    public void resetFilters() {
        Locator reset = page.locator("button:has-text('Сбросить фильтр'),"
                + " button:has-text('Сбросить фильтры'),"
                + " button:has-text('Очистить фильтры'),"
                + " a:has-text('Очистить фильтры'),"
                + " a:has-text('Сбросить фильтры')").first();
        reset.click();
        Locator allDirections = page.getByText("Все направления").first();
        if (allDirections.count() > 0) {
            allDirections.click(new Locator.ClickOptions().setForce(true));
        }
        waitForFiltersReset();
    }


    public List<Integer> getVisibleCourseDurations() {
        List<Integer> values = new ArrayList<>();
        Locator cards = getCourseCards();
        int count = (int) Math.min(cards.count(), 30);
        for (int i = 0; i < count; i++) {
            String text;
            try {
                text = cards.nth(i).innerText(new Locator.InnerTextOptions().setTimeout(2000));
            } catch (PlaywrightException ex) {
                continue;
            }
            Matcher matcher = DURATION_PATTERN.matcher(text);
            if (matcher.find()) {
                values.add(Integer.parseInt(matcher.group(1)));
            }
        }
        return values;
    }

    private Locator getFilterSection(String filterLabel) {
        Locator title = page.getByText(filterLabel).first();
        Locator section = title.locator("xpath=ancestor::*[self::div or self::section][1]");
        if (section.count() == 0) {
            section = title.locator("xpath=ancestor::*[self::div or self::section][2]");
        }
        return section;
    }

    private boolean isOptionChecked(String optionText) {
        Locator label = page.locator("label")
                .filter(new Locator.FilterOptions().setHasText(optionText))
                .first();
        if (label.count() > 0) {
            String forId = label.getAttribute("for");
            Locator input = forId != null
                    ? page.locator("input[id='" + forId + "']")
                    : label.locator("input");
            if (input.count() > 0) {
                return input.first().isChecked();
            }
        }
        Locator element = page.getByText(optionText).first();
        if (element.count() > 0) {
            String aria = element.getAttribute("aria-checked");
            if (aria == null) {
                aria = element.getAttribute("aria-pressed");
            }
            if (aria != null) {
                return "true".equals(aria);
            }
            Locator input = element.locator("input").first();
            if (input.count() > 0) {
                return input.isChecked();
            }
        }
        return false;
    }

    private Locator getCourseCards() {
        Locator list = page.locator("section, div").filter(
                new Locator.FilterOptions().setHasText("Показать еще")
        ).first();
        Locator cards = list.locator("a[href*='/lessons/']:visible")
                .filter(new Locator.FilterOptions().setHasNotText("Успеть!"));
        if (cards.count() == 0) {
            cards = list.locator("a:has(h4):visible, a:has(h5):visible, a:has(h6):visible");
        }
        if (cards.count() == 0) {
            cards = page.locator("main a[href*='/lessons/']:visible");
        }
        return cards;
    }

    private void dragSliderTo(Locator slider, int targetValue) {
        String minText = slider.getAttribute("aria-valuemin");
        String maxText = slider.getAttribute("aria-valuemax");
        int min = minText == null || minText.isBlank() ? 0 : Integer.parseInt(minText);
        int max = maxText == null || maxText.isBlank() ? 15 : Integer.parseInt(maxText);
        if (targetValue < min) {
            targetValue = min;
        }
        if (targetValue > max) {
            targetValue = max;
        }
        @SuppressWarnings("unchecked")
        Map<String, Object> trackBox = (Map<String, Object>) slider.evaluate("el => {"
                + "const track = el.parentElement;"
                + "const r = track.getBoundingClientRect();"
                + "return {x: r.x, y: r.y, width: r.width, height: r.height};"
                + "}");
        if (trackBox == null) {
            return;
        }
        double x = ((Number) trackBox.get("x")).doubleValue();
        double width = ((Number) trackBox.get("width")).doubleValue();
        double y = ((Number) trackBox.get("y")).doubleValue();
        double height = ((Number) trackBox.get("height")).doubleValue();
        double ratio = (targetValue - min) / (double) (max - min);
        double targetX = x + width * ratio;
        double targetY = y + height / 2;

        var handleBox = slider.boundingBox();
        if (handleBox == null) {
            return;
        }
        page.mouse().move(handleBox.x + handleBox.width / 2, handleBox.y + handleBox.height / 2);
        page.mouse().down();
        page.mouse().move(targetX, targetY, new Mouse.MoveOptions().setSteps(10));
        page.mouse().up();
    }

    private void nudgeSliderWithKeyboard(Locator slider, int targetValue) {
        String minText = slider.getAttribute("aria-valuemin");
        String maxText = slider.getAttribute("aria-valuemax");
        String currentText = slider.getAttribute("aria-valuenow");
        int min = minText == null || minText.isBlank() ? 0 : Integer.parseInt(minText);
        int max = maxText == null || maxText.isBlank() ? 15 : Integer.parseInt(maxText);
        int current = currentText == null || currentText.isBlank() ? min : Integer.parseInt(currentText);
        if (targetValue < min) {
            targetValue = min;
        }
        if (targetValue > max) {
            targetValue = max;
        }
        slider.focus();
        int steps = Math.abs(targetValue - current);
        String key = targetValue > current ? "ArrowRight" : "ArrowLeft";
        for (int i = 0; i < steps; i++) {
            page.keyboard().press(key);
        }
    }

    private void waitForDurationFilterApplied(int minMonths, int maxMonths) {
        for (int i = 0; i < 40; i++) {
            List<Integer> durations = getVisibleCourseDurations();
            if (!durations.isEmpty()
                    && durations.stream().allMatch(v -> v >= minMonths && v <= maxMonths)) {
                return;
            }
            page.waitForTimeout(300);
        }
    }

    private void waitForFiltersReset() {
        for (int i = 0; i < 20; i++) {
            if (isDefaultDirectionSelected() && getCourseCards().count() > 0) {
                return;
            }
            page.waitForTimeout(300);
        }
    }

    private void waitForSliderValues(Locator minSlider, Locator maxSlider, int minMonths, int maxMonths) {
        for (int i = 0; i < 20; i++) {
            String minValue = minSlider.getAttribute("aria-valuenow");
            String maxValue = maxSlider.getAttribute("aria-valuenow");
            if (minValue != null && maxValue != null) {
                try {
                    int min = Integer.parseInt(minValue);
                    int max = Integer.parseInt(maxValue);
                    if (min == minMonths && max == maxMonths) {
                        return;
                    }
                } catch (NumberFormatException ignored) {
                    // ignore and retry
                }
            }
            page.waitForTimeout(200);
        }
    }
}
