import pandas as pd
from shapely.geometry import Point, Polygon, LineString
from shapely.ops import transform
import pyproj
from folium import Map, GeoJson, Popup
from docx import Document
import numpy as np
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.shared import Pt
from datetime import datetime
import folium
from folium.features import GeoJson, DivIcon
from functools import partial
from branca.colormap import linear
from color_ansi import color_ansi
from shapely.geometry import mapping
from shapely.geometry import MultiLineString



def create_point(vertex, coord_df):
    try:
        coords = coord_df.loc[coord_df['PUNTO'] == vertex.strip(), ['ESTE', 'NORTE']].values[0]
        return Point(coords)
    except IndexError:
        print(f"No matching coordinates found for vertex: {vertex}")
        return None

def create_polygon(vertices, coord_df):
    points = [create_point(v, coord_df).coords[0] for v in vertices if v.strip() in coord_df['PUNTO'].values]
    return Polygon(points)

def calculate_distance(point1, point2):
    return np.sqrt((point2.x - point1.x)**2 + (point2.y - point1.y)**2)

def find_orientation(polygon_centroid, line):
    line_centroid = line.centroid
    vector = np.array([line_centroid.x, line_centroid.y]) - np.array([polygon_centroid.x, polygon_centroid.y])
    angle = np.degrees(np.arctan2(vector[1], vector[0]))
    
    if -45 <= angle < 45:
        return 'Oriente'
    elif 45 <= angle < 135:
        return 'Norte'
    elif -135 <= angle < -45:
        return 'Sur'
    else:
        return 'Occidente'

# def get_line_orientation(line):
#     if isinstance(line, MultiLineString):
#         # If you get multiple lines, handle them. Example: choose the longest
#         line = max(line.geoms, key=lambda part: part.length)
#         # line = line[0]
#     start, end = line.coords[0], line.coords[-1]
#     delta_x = end[0] - start[0]
#     delta_y = end[1] - start[1]
#     angle = np.degrees(np.arctan2(delta_y, delta_x))
    
#     if -45 <= angle <= 45 or 135 <= angle <= 180 or -180 <= angle <= -135:
#         return 'North-South'
#     else:
#         return 'East-West'

# def find_orientation(polygon_centroid, line):
#     line_orientation = get_line_orientation(line)
#     line_centroid = line.centroid
#     vector = np.array([line_centroid.x, line_centroid.y]) - np.array([polygon_centroid.x, polygon_centroid.y])
    
#     if line_orientation == 'East-West':
#         if vector[0] > 0:
#             return 'Oriente'
#         else:
#             return 'Occidente'
#     else:  # 'North-South'
#         if vector[1] > 0:
#             return 'Norte'
#         else:
#             return 'Sur'



def shorten_line(start_point, end_point, shorten_by):
    """ Shorten each end of the line segment by 'shorten_by' distance. """
    line_vector = np.array([end_point.x - start_point.x, end_point.y - start_point.y])
    line_length = np.linalg.norm(line_vector)
    line_direction = line_vector / line_length
    new_start_point = np.array(start_point.coords[0]) + line_direction * shorten_by
    new_end_point = np.array(end_point.coords[0]) - line_direction * shorten_by
    return LineString([new_start_point, new_end_point])


def clean_description(desc):
    # Remove ANSI codes for bold, etc., since they are not supported in HTML
    desc = desc.replace(color_ansi.BOLD, '<b>').replace(color_ansi.END, '</b>')
    return desc




def process_all(excel_path, header_text, footer_text):


# -------------------------------------
# ---- Process File -------------------
# -------------------------------------


    # Read the first sheet (Polygon Definitions)
    # Assuming the first four columns are '# Lote', 'Title', 'Area', and 'Vertices'
    polygon_df = pd.read_excel(excel_path, sheet_name=0, usecols="A:D")

    # Read the second sheet (Coordinates)
    # Assuming the coordinates are in a format suitable for mapping to vertices
    coordinates_df = pd.read_excel(excel_path, sheet_name=1)

    # Print the first few rows to verify the data
    print("Polygon Definitions:")
    print(polygon_df.head())
    print("\nCoordinates:")
    print(coordinates_df.head())

   

# -------------------------------------
# ---- Create descriptions ------------
# -------------------------------------

    # Create polygons and store centroids
    polygon_df['Vertices'] = polygon_df['Puntos de coordenadas'].str.split('-')
    coordinates_df['PUNTO'] = coordinates_df['PUNTO'].astype(str)
    polygon_df['# Lote'] = polygon_df['# Lote'].astype(str)
    polygon_df['Polygon'] = polygon_df['Vertices'].apply(lambda x: create_polygon(x, coordinates_df))
    polygon_df['Centroid'] = polygon_df['Polygon'].apply(lambda poly: poly.centroid)


    # Adding a buffer and checking for adjacencies with the buffer zone
    buffer_distance = 1  # 1 meter buffer
    adjacencies = {i: {} for i in polygon_df.index}

    for index, row in polygon_df.iterrows():
        vertices = row['Vertices']
        for j in range(len(vertices) - 1):
            start_point = create_point(vertices[j], coordinates_df)
            end_point = create_point(vertices[j+1], coordinates_df)
            line = LineString([start_point, end_point])
            short_line = shorten_line(start_point, end_point, buffer_distance*2.5)
            buffered_line = short_line.buffer(buffer_distance)  # Apply a buffer to create a "thick" line
            distance = calculate_distance(start_point, end_point)
            line_description = f"partiendo del punto {vertices[j]} (E {start_point.x}, N {start_point.y}) al punto {vertices[j+1]} (E {end_point.x}, N {end_point.y}) en línea recta y en distancia de {distance:.2f} metros lineales; "

            # Compare with all other polygons for adjacency
            for idx, other_row in polygon_df.iterrows():
                if index != idx and buffered_line.intersects(other_row['Polygon'].boundary):
                    shared_line = buffered_line.intersection(other_row['Polygon'].boundary)
                    if not shared_line.is_empty:
                        orientation = find_orientation(row['Centroid'], shared_line)
                        if orientation not in adjacencies[index]:
                            adjacencies[index][orientation] = []
                        adjacencies[index][orientation].append(line_description + f"y que linda con {other_row['Tipo']} número {other_row['# Lote']}")                        


    # Generating descriptions with complete details
    descriptions = []
    clean_descriptions = []
    for index, row in polygon_df.iterrows():
        description = f"{color_ansi.BOLD}{row['Tipo'].upper()} NÚMERO {row['# Lote']} DE {row['Etapa']}:{color_ansi.END} {header_text}"
        description += f"con un área privada total aproximada de {row['Polygon'].area:.2f} metros cuadrados. "
        description += "Su área y linderos están determinados por el perímetro marcado con los siguientes puntos:"
        
        # for orientation, detail in adjacencies[index].items():
        #     description += f"POR EL {color.BOLD}{orientation.upper()}:{color.END} {detail}"
        
        for orientation, details in adjacencies[index].items():
            # Join the list items into a single string with proper formatting
            details_str = "; ".join(details)  # Use a semicolon and a space to separate each detail
            description += f" {color_ansi.BOLD}POR EL {orientation.upper()}:{color_ansi.END} {details_str}."


        description += " " + footer_text
        descriptions.append(description)
        clean_descriptions.append(clean_description(description))

    polygon_df['Description'] = clean_descriptions

    # Display the first few descriptions to verify
    for desc in descriptions:
        print(desc)

    
# ---------------------------------
# ---- Create doc file ------------
# ---------------------------------
    
    # Create a new Document
    doc = Document()

    # Define the header content
    header_content = ("Documento creado automaticamente con AutoWriter de Arquitectura & Concreto el "
                    f"{datetime.now().strftime('%d/%m/%Y')}.\n"
                    "Los resultados de este documento deben ser validados objetivamente antes de ser incluidos en "
                    "las escrituras finales o cualquier otro tipo de documento oficial.\n\n")

    # Apply formatting function
    def apply_formatting(paragraph, text):
        parts = text.split(color_ansi.BOLD)  # Splits the text at the start of bold
        for part in parts:
            if color_ansi.END in part:
                bold_text, normal_text = part.split(color_ansi.END, 1)
                run = paragraph.add_run(bold_text)
                run.bold = True
                paragraph.add_run(normal_text)
            else:
                paragraph.add_run(part)

    for desc in descriptions:
        paragraph = doc.add_paragraph()
        apply_formatting(paragraph, desc)
        paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY  # Justify the text
        
        # Add the separator line
        doc.add_paragraph("-------------------------").alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
        
        # Add an empty paragraph for a new line (optional, for extra spacing)
        doc.add_paragraph()

    # Adding headers with dynamic dates and setting font size
    for section in doc.sections:
        header = section.header
        header_paragraph = header.paragraphs[0] if header.paragraphs else header.add_paragraph()
        header_paragraph.text = header_content
        header_paragraph.style = doc.styles['Header']
        
        # Set font size to 8 pt for the header
        for run in header_paragraph.runs:
            run.font.size = Pt(10)
    # Save the document
    word_file_path = 'downloads/Descripciones Escrituras.docx'
    doc.save(word_file_path)

    print("Word document has been created successfully!")

# ---------------------------------
# ---- Create map file ------------
# ---------------------------------


    map_folium = None
    # Set up the projection transformations
    source_crs = pyproj.CRS('EPSG:3116')  # Replace 'xxxx' with the EPSG code for Colombian National Projection
    #source_crs = pyproj.CRS('EPSG:4686')  # Replace 'xxxx' with the EPSG code for Colombian National Projection
    target_crs = pyproj.CRS('EPSG:4326')  # EPSG for WGS 84
    project = pyproj.Transformer.from_crs(source_crs, target_crs, always_xy=True).transform

    # Apply the transformation to each polygon and update the dataframe
    #polygon_df['Polygon'] = polygon_df['Polygon'].apply(project_and_reverse_coords)
    # Apply the transformation to each polygon

    polygon_df['Polygon_WGS'] = polygon_df['Polygon'].apply(lambda poly: transform(project, poly))

    # Recalculate centroids after projection transformation
    polygon_df['Centroid'] = polygon_df['Polygon_WGS'].apply(lambda poly: poly.centroid)
    average_lat = polygon_df['Centroid'].apply(lambda p: p.y).mean()
    average_lon = polygon_df['Centroid'].apply(lambda p: p.x).mean()
    map_center = [average_lat, average_lon]

    map_folium = folium.Map(location=map_center, zoom_start=18)

    # Step 1: Create a color map
    etapas = polygon_df['Etapa'].unique()
    colors = linear.Set1_09.scale(0, len(etapas))  # Adjust the color palette as needed
    color_map = {etapa: colors(i) for i, etapa in enumerate(etapas)}

    # Step 2: Add polygons to the map with colors
    for index, row in polygon_df.iterrows():
        geo_json = mapping(row['Polygon_WGS'])
        style_function = lambda x, color=color_map[row['Etapa']]: {"fillColor": color, "color": "black", "weight": 2, "fillOpacity": 0.5}
        popup_content = folium.Popup(row['Description'], max_width=300)
        folium.GeoJson(geo_json, style_function=style_function, popup=popup_content).add_to(map_folium)
        label_location = [row['Centroid'].y, row['Centroid'].x]
        label = f"{row['# Lote']}"
        folium.Marker(
            location=label_location,
            icon=DivIcon(
                #icon_size=(150,36),
                #icon_anchor=(0,0),
                html=f"""<div style="font-size: 6pt; color: black"><b>{label}</b></div>""",
            )
        ).add_to(map_folium)

    # Step 3: Create a dynamic legend
    legend_html = '<div style="position: fixed; bottom: 50px; left: 50px; width: 150px; height: %s; border:2px solid grey; z-index:9999; font-size:14px; background-color: white; padding: 10px;">' % (str(30 + len(etapas) * 20))
    legend_html += '<p><b>Etapa Legend:</b></p>'
    for etapa, color in color_map.items():
        legend_html += '<p><span style="color: %s;">&#9632;</span> %s</p>' % (color, etapa)
    legend_html += '</div>'

    # Add the legend to the map
    map_folium.get_root().html.add_child(folium.Element(legend_html))

    # Save the map
    map_file_path = 'downloads/mapa.html'
    map_folium.save(map_file_path)
    print("Interactive map has been created and saved successfully!")

    return word_file_path, map_file_path
