/*
 * RiskScape™ Copyright New Zealand Institute for Earth Science Limited
 * (Earth Sciences New Zealand) is distributed for research purposes only
 * under the terms of AGPLv3.
 *
 * RiskScape™ Copyright 2026 New Zealand Institute for Earth Science
 * Limited (Earth Sciences New Zealand). All rights reserved. Source code
 * available under the AGPLv3.
 * 
 * This program is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU Affero General Public License as published by the Free
 *  Software Foundation, either version 3 of the License, or (at your option) any
 *  later version.
 * 
 * This program is distributed for RESEARCH PURPOSES ONLY, in the hope that it will
 * be useful for research and education initiatives.
 * 
 * If you are not a researcher, or you are a researcher who wishes to use this
 * program on terms other than AGPLv3 (including those who wish to restrict the
 * distribution of any source code created using this program), please contact:
 * https://riskscape.org.nz
 * 
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.  You should have received a copy
 * of the GNU Affero General Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * By way of summary only, under the AGPLv3:
 *     • Permissions of this strongest copyleft license are conditioned
 *       on making available complete source code of licensed works and
 *       modifications, which include larger works using a licensed work,
 *       under the same license.
 *     • Copyright and license notices must be preserved.
 *     • Contributors provide an express grant of patent rights.
 *     • When a modified version is used to provide a service over a
 *       network, the complete source code of the modified version must be made
 *       available.
 */
package nz.org.riskscape.wizard;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import org.junit.Test;
import org.junit.Before;

import nz.org.riskscape.engine.ProjectTest;
import nz.org.riskscape.engine.cli.TerminalTestHelper;
import nz.org.riskscape.engine.cli.TestTerminal;
import nz.org.riskscape.engine.i18n.MutableMessageSource;
import nz.org.riskscape.engine.i18n.TranslationContext;
import nz.org.riskscape.rl.ast.Expression;
import nz.org.riskscape.wizard.ask.AskAsAsker;
import nz.org.riskscape.wizard.ask.Asker;
import nz.org.riskscape.wizard.ask.BooleanExpressionAsker;
import nz.org.riskscape.wizard.ask.DefaultAsker;
import nz.org.riskscape.wizard.bld.IncrementalBuildState;
import nz.org.riskscape.wizard.model2.input.FilterByAttribute;
import nz.org.riskscape.wizard.survey2.QuestionTree;

/**
 * Checks the CliPrompter formats the prompt correctly, i.e. line-wraps the
 * wizard text in the correct place.
 */
public class CliPrompterTest extends ProjectTest {

  TerminalTestHelper terminalHelper = new TerminalTestHelper();
  TestTerminal terminal = terminalHelper.terminal;
  MutableMessageSource messageSource = new MutableMessageSource();
  TranslationContext translationContext = new TranslationContext(Locale.getDefault(), messageSource);
  List<Asker> askers = Collections.emptyList();

  CliPrompter prompter;

  @Before
  public void createPrompter() {
    // Note that prompter width defaults to 80 chars and doesn't resize
    prompter = new CliPrompter(
            terminal,
            askers,
            new DefaultAsker(translationContext),
            messages,
            translationContext
    );
  }


  @Test
  public void canFormatAShortTitleCorrectly() throws Exception {
    CliChoice<String> choice = new CliChoice<>("title", "foo")
        .subtitled("This bit gets indented for consistent appearance. Line-wrap it to 80 chars too please!");

    chooseOne(choice);

    // we pad short titles so things are better aligned and have a more consistent appearance
    assertThat(getOutput(), allOf(
        hasItem("1:  title:      This bit gets indented for consistent appearance. Line-wrap it "),
        hasItem("                to 80 chars too please!")
    ));
  }

  @Test
  public void canLineWrapALongTitleCorrectly() throws Exception {
    CliChoice<String> choice = new CliChoice<>("this title exceeds the indent", "foo")
        .subtitled("The quick brown fox jumps over the lazy dog. That's enough for one line");

    chooseOne(choice);

    // choice text should get line-wrapped at 80 chars, with the next line indented
    assertThat(getOutput(), allOf(
        hasItem("1:  this title exceeds the indent:  The quick brown fox jumps over the lazy dog."),
        hasItem("                That's enough for one line")
        ));
  }

  @Test
  public void printsOutBreadcrumbUsingAQuestionsSummary() throws Exception {
    EmptySurvey survey = new EmptySurvey("empty", messageSource);
    TestQuestionSet questions = new TestQuestionSet("set", survey);

    Question questionOne = questions.add(new Question("foo", String.class));
    Question questionTwo = questions.add(new Question("bar-baz", String.class).dependsOn(questionOne));

    // set a message for foo, but not bar
    messageSource.addMessage("question.set.foo.summary", "Gamma Rays");

    QuestionTree tree = QuestionTree.fromList(Arrays.asList(questionOne, questionTwo));
    prompter.printBreadcrumb(tree, questionOne);
    prompter.printBreadcrumb(tree, questionTwo);

    assertThat(
      terminalHelper.outStream.getLines(),
      contains(
          // first had a summary, second fell back to name
        equalTo("set >> Gamma Rays"),
        equalTo(""),
        equalTo("set >> Gamma Rays >> Bar baz"),
        equalTo("")
      )
    );

  }

  @Test
  public void usesBooleanExpressionAskerBeforeAskAs() {
    // Get the real list of askers
    askers = WizardCommand.getAskers(translationContext);
    createPrompter();

    Question question = new Question("filter", Expression.class).withAskAs(FilterByAttribute.ASK_AS);

    IncrementalBuildState buildState = mock(IncrementalBuildState.class);

    // Make sure that the askAsAsker can show up...
    assertThat(prompter.findAsker(buildState, question), instanceOf(AskAsAsker.class));

    // ... but not if the boolean expression asker could answer it
    question = question.withAnnotations(
            ExpressionHelper.TAG_EXPRESSION_TYPE, ExpressionHelper.TAG_EXPRESSION_TYPE_BOOLEAN
    );
    assertThat(prompter.findAsker(buildState, question), instanceOf(BooleanExpressionAsker.class));
  }

  private <T> void chooseOne(CliChoice<T> pickMe) {
    // there's only one choice, so populate the input stream with our decision
    terminalHelper.inputStream.setBytes("1\n".getBytes());

    CliChoice<T> picked = prompter.choose(terminal.getAnsiOut().applyStyles("Test prompt"), Arrays.asList(pickMe));
    assertThat(picked, is(pickMe));
  }

  private List<String> getOutput() throws Exception {
    // check we line-wrapped the output correctly while we're at it
    for (String line : terminalHelper.outStream.getLines()) {
      assertTrue(line.length() <= terminal.getDisplayWidth());
    }
    return terminalHelper.outStream.getLines();
  }
}
