package CH.ifa.draw.figures;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.IOException;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import CH.ifa.draw.framework.ParentFigure;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Unit tests for {@link TextFigure}.
 * Increases coverage for text splitting, attribute handling, and drawing logic.
 */
public class TextFigureTest {

    private TextFigure _testInstance;

    @Mock
    private Graphics _mockGraphics;

    @Mock
    private FontMetrics _mockFontMetrics;

    @Mock
    private ParentFigure _mockParent;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
        _testInstance = new TextFigure();

        when(_mockGraphics.getFontMetrics(any(Font.class))).thenReturn(_mockFontMetrics);
        when(_mockGraphics.getFontMetrics()).thenReturn(_mockFontMetrics);

        when(_mockFontMetrics.stringWidth(any(String.class))).thenReturn(50);
        when(_mockFontMetrics.getHeight()).thenReturn(10);
        when(_mockFontMetrics.getAscent()).thenReturn(8);
    }

    @Test
    public void testDefaultConstructor() {
        //when
        TextFigure textFigure = new TextFigure();

        //then
        assertEquals("", textFigure.getText(), "Default constructor should have empty text");
    }

    @Test
    public void testConstructorWithText() {
        //when
        TextFigure textFigure = new TextFigure("Hello");

        //then
        assertEquals("Hello", textFigure.getText(), "Constructor with text failed");
    }

    @Test
    public void testConstructorWithTextAndReadOnlyFlag() {
        //when
        TextFigure textFigure = new TextFigure("ReadOnly", true);

        //then
        assertEquals("ReadOnly", textFigure.getText());
        assertTrue(textFigure.readOnly(), "ReadOnly flag should be true");
    }

    @Test
    public void testTextSplittingWithMultipleLines() {
        //given
        _testInstance.setText("Line1\nLine2");

        //when
        String[] lines = _testInstance.getLines();

        //then
        assertNotNull(lines);
        assertEquals(2, lines.length);
        assertEquals("Line1", lines[0]);
        assertEquals("Line2", lines[1]);
    }

    @Test
    public void testTextSplittingWithTrailingNewline() {
        //given
        _testInstance.setText("Line1\n");

        //when
        String[] lines = _testInstance.getLines();

        //then
        assertNotNull(lines);
        assertEquals(2, lines.length, "Should include empty line for trailing newline");
        assertEquals("Line1", lines[0]);
        assertEquals("", lines[1]);
    }

    @Test
    public void testAttributeHandling() {
        //when
        _testInstance.setAttribute("FontSize", 24);
        _testInstance.setAttribute("FontName", "Serif");
        _testInstance.setAttribute("FontStyle", Font.BOLD);
        _testInstance.setAttribute("TextAlignment", TextFigure.CENTER);
        _testInstance.setAttribute("FillColor", Color.RED);

        //then
        assertEquals(24, _testInstance.getAttribute("FontSize"));
        assertEquals("Serif", _testInstance.getAttribute("FontName"));
        assertEquals(Font.BOLD, _testInstance.getAttribute("FontStyle"));
        assertEquals(TextFigure.CENTER, _testInstance.getAlignment());
        assertEquals(Color.RED, _testInstance.getFillColor());
    }

    @Test
    public void testBasicMoveByAndDisplayBox() {
        //given
        _testInstance.basicDisplayBox(new Point(10, 10), new Point(20, 20));

        //when
        _testInstance.moveBy(5, 5);
        Rectangle r = _testInstance.displayBox();

        //then
        assertEquals(new Point(15, 15), _testInstance.getOrigin());
        assertNotNull(r);
        assertEquals(15, r.x);
        assertEquals(15, r.y);
    }

    @Test
    public void testMoveByWithParent() {
        //given
        de.renew.draw.storables.ontology.Locator realLocator =
            CH.ifa.draw.standard.RelativeLocator.northWest();
        when(_mockParent.connectedTextLocator(any())).thenReturn(realLocator);
        when(_mockParent.displayBox()).thenReturn(new Rectangle(0, 0, 100, 100));

        _testInstance.setText("Test");
        _testInstance.setParent(_mockParent);

        Point originBefore = _testInstance.getOrigin();

        //when
        _testInstance.moveBy(10, 10);

        //then
        assertEquals(originBefore.x + 10, _testInstance.getOrigin().x, "X should be moved by 10");
        assertEquals(originBefore.y + 10, _testInstance.getOrigin().y, "Y should be moved by 10");
    }

    @Test
    public void testInternalDrawCallsGraphics() {
        //given
        _testInstance.setText("TestLine");

        //when
        _testInstance.internalDraw(_mockGraphics);

        //then
        verify(_mockGraphics, atLeastOnce()).getFontMetrics(any(Font.class));

        verify(_mockGraphics).drawString(eq("TestLine"), anyInt(), anyInt());
    }

    @Test
    public void testContainsPoint() {
        //given
        _testInstance.setText("Hit");
        _testInstance.basicDisplayBox(new Point(0, 0), null); //Origin at 0,0

        //when
        boolean containsOutside = _testInstance.containsPoint(100, 100);
        boolean containsInside = _testInstance.containsPoint(5, 5);

        //then
        assertFalse(containsOutside, "Should not contain point way outside");
        assertTrue(containsInside, "Should contain point inside the text box");
    }

    @Test
    public void testCanBeParent() {
        //given
        CH.ifa.draw.framework.Locator mockLocator = mock(CH.ifa.draw.framework.Locator.class);
        doReturn(new Point(0, 0)).when(mockLocator).locate(any());
        doReturn(mockLocator).when(_mockParent).connectedTextLocator(_testInstance);

        //when
        boolean canBeParentMock = _testInstance.canBeParent(_mockParent);
        boolean canBeParentSelf = _testInstance.canBeParent(_testInstance);

        //then
        assertTrue(canBeParentMock);
        assertFalse(canBeParentSelf, "Cannot be parent of itself");
    }

    @Test
    public void testSetParentAndRelease() {
        //given
        CH.ifa.draw.framework.Locator mockLocator = mock(CH.ifa.draw.framework.Locator.class);
        doReturn(new Point(0, 0)).when(mockLocator).locate(any());
        doReturn(mockLocator).when(_mockParent).connectedTextLocator(_testInstance);

        //when
        _testInstance.setParent(_mockParent);

        //then
        assertEquals(_mockParent, _testInstance.parent());
        verify(_mockParent).addChild(_testInstance);

        //when
        _testInstance.release();

        //then
        verify(_mockParent).removeChild(_testInstance);
    }

    @Test
    public void testWriteStorable() throws IOException {
        //given
        StorableOutput mockOut = mock(StorableOutput.class);
        _testInstance.setText("SaveMe");
        _testInstance.setAttribute("FontSize", 14);

        //when
        _testInstance.write(mockOut);

        //then
        verify(mockOut).writeString("SaveMe");
        verify(mockOut).writeInt(14); //FontSize
    }

    @Test
    public void testReadStorable() throws IOException {
        //given
        StorableInput mockIn = mock(StorableInput.class);
        when(mockIn.readInt()).thenReturn(99, 10, 20, Font.PLAIN, 12);
        when(mockIn.readString()).thenReturn("IgnoredAttribute", "LoadedText", "Helvetica");
        when(mockIn.readBoolean()).thenReturn(false); //isReadOnly
        when(mockIn.getVersion()).thenReturn(8);

        //when
        _testInstance.read(mockIn);

        //then
        int x = _testInstance.getOrigin().x;
        assertTrue(
            x == 10 || x == 99, "X coordinate was not read correctly from stream. Got: " + x);
        assertNotNull(_testInstance.getText());
    }

    @Test
    public void testOverlayColumnsDefaultWidth() {
        //given
        _testInstance.setText("Short");

        //when
        int width = _testInstance.overlayRowsAndColumns().width;

        //then
        assertEquals(20, width, "Short text should default to 20 columns");
    }

    @Test
    public void testOverlayColumnsForLongText() {
        //given
        _testInstance.setText("VeryLongLineThatExceedsTheDefaultTwentyColumns");

        //when
        int width = _testInstance.overlayRowsAndColumns().width;

        //then
        assertTrue(width > 20, "Long text should exceed the default 20 columns");
    }

    @Test
    public void testOverlayRowsForMultipleLines() {
        //given
        _testInstance.setText("Row1\nRow2\nRow3");

        //when
        int height = _testInstance.overlayRowsAndColumns().height;

        //then
        assertEquals(3, height, "Height should match the number of rows");
    }

    @Test
    public void testHandles() {

        //given/when
        java.util.Vector handles = _testInstance.handles();

        //then
        assertNotNull(handles);
        assertEquals(4, handles.size());
    }

    @Test
    public void testGetLineBox() {
        //given
        _testInstance.setText("TestLine");
        _testInstance.basicDisplayBox(new Point(10, 10), null);

        //when
        Rectangle lineBox = _testInstance.getLineBox(_mockGraphics, 0);

        //then
        assertNotNull(lineBox, "Line box should not be null");

        Rectangle expectedRectangle = new Rectangle(0, 0, 50, 10);
        assertEquals(expectedRectangle, lineBox);
    }

}