import os
import pathlib
from datetime import datetime

import chardet
import pandas as pd
from qgis.PyQt.QtCore import QThread, pyqtSignal, QVariant
from qgis.PyQt import QtWidgets, uic, QtCore
from qgis.PyQt.QtWidgets import QApplication
from qgis.core import (
    QgsProject, QgsVectorLayer, QgsPointXY, QgsGeometry, QgsFeature,
    QgsSingleSymbolRenderer, QgsField, QgsFields, QgsMarkerSymbol
)
from qgis.utils import iface
import openpyxl

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'cyanlove_import_create_point_base.ui'))


class cyanlove_createpoint(QtWidgets.QDockWidget, FORM_CLASS):
    closingPlugin = pyqtSignal()

    def __init__(self, parent=None):
        super(cyanlove_createpoint, self).__init__(parent)
        self.export_thread = None
        self.header_thread = None
        self.current_file_path = ""
        self.is_reading_headers = False   # 新增标志
        self.setupUi(self)
        self.setFloating(True)
        self.mQgsFileWidget.setFilter("*.csv;*.xlsx")
        self.setWindowFlags(QtCore.Qt.Dialog)

        self.mQgsFileWidget.fileChanged.connect(self.selectfilepath)
        self.pushButton.clicked.connect(self.writer_point)

        self.clear_comboboxes()
        self.pushButton.setEnabled(True)   # 初始可用

    def closeEvent(self, event):
        if self.header_thread and self.header_thread.isRunning():
            self.header_thread.quit()
            self.header_thread.wait()
        if self.export_thread and self.export_thread.isRunning():
            self.export_thread.quit()
            self.export_thread.wait()
        self.closingPlugin.emit()
        event.accept()

    def update_label(self, value):
        self.label_show.setText(value)
        QApplication.processEvents()

    def clear_comboboxes(self):
        self.comboBox_lon.clear()
        self.comboBox_lat.clear()
        self.comboBox_wkt.clear()

    def selectfilepath(self):
        """异步读取文件表头，避免卡界面，同时控制按钮状态"""
        filepath = self.mQgsFileWidget.filePath()
        if not filepath:
            self.clear_comboboxes()
            self.label_show.setText("没有选择文件")
            self.pushButton.setEnabled(True)
            self.is_reading_headers = False
            return

        if filepath == self.current_file_path and not self.is_reading_headers:
            # 同一文件且已读取完，无需重复
            return

        if not os.path.exists(filepath):
            self.clear_comboboxes()
            self.label_show.setText("文件不存在，请重新选择")
            self.pushButton.setEnabled(True)
            self.is_reading_headers = False
            return

        # 更新路径
        self.current_file_path = filepath
        self.clear_comboboxes()
        self.label_show.setText("正在读取文件表头...")
        self.pushButton.setEnabled(False)          # 禁用创建按钮
        self.is_reading_headers = True

        # 如果已有线程在运行，终止
        if self.header_thread and self.header_thread.isRunning():
            self.header_thread.quit()
            self.header_thread.wait()

        # 启动新线程
        self.header_thread = HeaderReaderThread(filepath)
        self.header_thread.headers_ready.connect(self.on_headers_ready)
        self.header_thread.error_occurred.connect(self.on_header_error)
        self.header_thread.start()

    def on_headers_ready(self, headers):
        """表头读取完成，更新下拉框并恢复按钮"""
        self.is_reading_headers = False
        self.pushButton.setEnabled(True)
        if not headers:
            self.label_show.setText("文件没有列名")
            return
        self.comboBox_lon.addItems(headers)
        self.comboBox_lat.addItems(headers)
        self.comboBox_wkt.addItems(headers)
        self.label_show.setText(f"成功读取，共 {len(headers)} 个字段")

    def on_header_error(self, error_msg):
        """读取表头失败，恢复按钮"""
        self.is_reading_headers = False
        self.pushButton.setEnabled(True)
        self.label_show.setText(f"读取表头失败: {error_msg}")

    def writer_point(self):
        """启动工作线程，线程内完成数据读取和处理"""
        # 检查是否正在读取表头
        if self.is_reading_headers:
            self.label_show.setText("正在读取表头，请稍后...")
            return

        filepath = self.mQgsFileWidget.filePath()
        if not filepath or not os.path.exists(filepath):
            self.label_show.setText("请选择有效文件")
            return

        if self.comboBox_lon.count() == 0:
            self.label_show.setText("请先选择包含数据的文件")
            return

        timestamp = datetime.now().strftime("%H%M%S")
        base_filename = os.path.splitext(os.path.basename(filepath))[0]
        layname = f'{base_filename}_Point_{timestamp}'

        lngcolumn = self.comboBox_lon.currentText()
        latcolumn = self.comboBox_lat.currentText()
        wktcolumn = self.comboBox_wkt.currentText()
        use_wkt = self.checkBox_iswkt.isChecked()

        if use_wkt:
            if not wktcolumn:
                self.label_show.setText("请选择WKT字段")
                return
        else:
            if not lngcolumn or not latcolumn:
                self.label_show.setText("请选择经度和纬度字段")
                return

        # 移除已存在的同名图层
        for layer in QgsProject.instance().mapLayersByName(layname):
            QgsProject.instance().removeMapLayer(layer.id())

        # 创建内存图层
        layer = QgsVectorLayer("Point?crs=EPSG:4326", layname, "memory")
        if not layer.isValid():
            self.label_show.setText("创建图层失败")
            return

        selected_color = self.mColorButton.color().name()

        # 启动工作线程（传入文件路径，由线程自行读取）
        self.export_thread = OptimizedExportThread(
            filepath, layer, selected_color,
            lngcolumn, latcolumn, wktcolumn, use_wkt
        )
        self.export_thread.updatelabel.connect(self.update_label)
        self.export_thread.layerAdded.connect(lambda: QgsProject.instance().addMapLayer(layer, True))
        self.export_thread.layerAdded.connect(iface.mapCanvas().refresh)
        self.export_thread.start()


class HeaderReaderThread(QThread):
    headers_ready = pyqtSignal(list)
    error_occurred = pyqtSignal(str)

    def __init__(self, filepath):
        super().__init__()
        self.filepath = filepath

    def run(self):
        try:
            ext = os.path.splitext(self.filepath)[1].lower()
            if ext == '.csv':
                headers = self._read_csv_headers()
            elif ext == '.xlsx':
                headers = self._read_excel_headers()
            else:
                self.error_occurred.emit("不支持的文件格式")
                return
            self.headers_ready.emit(headers)
        except Exception as e:
            self.error_occurred.emit(f"读取表头失败: {e}")

    def _read_csv_headers(self):
        with open(self.filepath, 'rb') as f:
            raw = f.read(2000)
            enc = chardet.detect(raw)['encoding']
            if enc and enc.lower() in ['gb2312', 'gb18030']:
                enc = 'gbk'
        df = pd.read_csv(self.filepath, encoding=enc, nrows=0, engine='c')
        return df.columns.tolist()

    def _read_excel_headers(self):
        wb = openpyxl.load_workbook(self.filepath, read_only=True)
        sheet = wb.active
        headers = []
        for row in sheet.iter_rows(min_row=1, max_row=1, values_only=True):
            headers = [str(cell) if cell is not None else '' for cell in row]
            break
        wb.close()
        return headers


class OptimizedExportThread(QThread):
    progress = pyqtSignal(int)
    layerAdded = pyqtSignal()
    updatelabel = pyqtSignal(str)

    def __init__(self, filepath, layer, color, lngcol, latcol, wktcol, use_wkt):
        super().__init__()
        self.filepath = filepath
        self.layer = layer
        self.color = color
        self.lngcol = lngcol
        self.latcol = latcol
        self.wktcol = wktcol
        self.use_wkt = use_wkt

    def run(self):
        try:
            self.updatelabel.emit("正在读取数据文件...")
            df = self._read_data()
            if df is None or df.empty:
                self.updatelabel.emit("数据文件为空或读取失败")
                return

            if self.use_wkt:
                self._process_wkt(df)
            else:
                self._process_points(df)

        except Exception as e:
            self.updatelabel.emit(f"处理失败: {str(e)}")
            import traceback
            traceback.print_exc()

    def _read_data(self):
        ext = os.path.splitext(self.filepath)[1].lower()
        if ext == '.csv':
            with open(self.filepath, 'rb') as f:
                raw = f.read(2000)
                enc = chardet.detect(raw)['encoding']
                if enc and enc.lower() in ['gb2312', 'gb18030']:
                    enc = 'gbk'
            return pd.read_csv(self.filepath, encoding=enc, engine='c', low_memory=False)
        elif ext == '.xlsx':
            return self._read_excel_fast()
        else:
            self.updatelabel.emit("不支持的文件格式")
            return None

    def _read_excel_fast(self):
        wb = openpyxl.load_workbook(self.filepath, read_only=True, data_only=True)
        sheet = wb.active
        rows = sheet.iter_rows(values_only=True)
        headers = [str(cell) if cell is not None else '' for cell in next(rows)]
        data = []
        for row in rows:
            data.append([str(cell) if cell is not None else '' for cell in row])
        wb.close()
        return pd.DataFrame(data, columns=headers)

    def _process_points(self, df):
        self.updatelabel.emit("预处理坐标数据...")
        df[self.lngcol] = pd.to_numeric(df[self.lngcol], errors='coerce')
        df[self.latcol] = pd.to_numeric(df[self.latcol], errors='coerce')
        valid = (~df[self.lngcol].isna()) & (~df[self.latcol].isna())
        df = df[valid].reset_index(drop=True)
        count = len(df)
        if count == 0:
            self.updatelabel.emit("没有有效的坐标数据")
            return

        self.updatelabel.emit(f"有效数据 {count} 条，开始创建点...")
        provider = self.layer.dataProvider()
        fields = QgsFields()
        for col in df.columns:
            fields.append(QgsField(col, QVariant.String))
        provider.addAttributes(fields)
        self.layer.updateFields()

        symbol = QgsMarkerSymbol.createSimple({'name': 'circle', 'color': self.color, 'size': '4'})
        self.layer.setRenderer(QgsSingleSymbolRenderer(symbol))

        features = []
        batch_size = 2000
        valid_count = 0
        error_count = 0
        col_names = df.columns.tolist()
        lng_idx = col_names.index(self.lngcol)
        lat_idx = col_names.index(self.latcol)

        for row_tuple in df.itertuples(index=False):
            lng = row_tuple[lng_idx]
            lat = row_tuple[lat_idx]
            if pd.isna(lng) or pd.isna(lat):
                continue
            try:
                geom = QgsGeometry.fromPointXY(QgsPointXY(float(lng), float(lat)))
                if not geom.isGeosValid():
                    continue
                feat = QgsFeature()
                feat.setFields(fields)
                feat.setGeometry(geom)
                attrs = [str(v) if not pd.isna(v) else '' for v in row_tuple]
                feat.setAttributes(attrs)
                features.append(feat)
                valid_count += 1
                if len(features) >= batch_size:
                    provider.addFeatures(features)
                    features.clear()
                if (valid_count % 1000 == 0) or (valid_count == count):
                    prog = int(valid_count / count * 100)
                    self.updatelabel.emit(f"进度：{prog}%，已处理 {valid_count}")
            except Exception as e:
                error_count += 1
                if error_count <= 10:
                    self.updatelabel.emit(f"行错误: {str(e)}")
                continue

        if features:
            provider.addFeatures(features)

        self.layer.triggerRepaint()
        self.layer.updateExtents()
        self.layerAdded.emit()
        self.updatelabel.emit(f"完成！成功 {valid_count} 个，失败 {error_count} 个")

    def _process_wkt(self, df):
        self.updatelabel.emit("处理 WKT 数据...")
        provider = self.layer.dataProvider()
        fields = QgsFields()
        for col in df.columns:
            fields.append(QgsField(col, QVariant.String))
        provider.addAttributes(fields)
        self.layer.updateFields()

        symbol = QgsMarkerSymbol.createSimple({'name': 'circle', 'color': self.color, 'size': '4'})
        self.layer.setRenderer(QgsSingleSymbolRenderer(symbol))

        features = []
        batch_size = 2000
        valid_count = 0
        error_count = 0
        col_names = df.columns.tolist()
        wkt_idx = col_names.index(self.wktcol)
        count = len(df)

        for row_tuple in df.itertuples(index=False):
            wkt = row_tuple[wkt_idx]
            if pd.isna(wkt) or str(wkt).strip() == '':
                continue
            try:
                geom = QgsGeometry.fromWkt(str(wkt))
                if not geom.isGeosValid() or geom.isEmpty():
                    continue
                feat = QgsFeature()
                feat.setFields(fields)
                feat.setGeometry(geom)
                attrs = [str(v) if not pd.isna(v) else '' for v in row_tuple]
                feat.setAttributes(attrs)
                features.append(feat)
                valid_count += 1
                if len(features) >= batch_size:
                    provider.addFeatures(features)
                    features.clear()
                if (valid_count % 1000 == 0) or (valid_count == count):
                    prog = int(valid_count / count * 100)
                    self.updatelabel.emit(f"进度：{prog}%，有效要素：{valid_count}")
            except Exception as e:
                error_count += 1
                if error_count <= 10:
                    self.updatelabel.emit(f"WKT行错误: {str(e)}")
                continue

        if features:
            provider.addFeatures(features)

        self.layer.triggerRepaint()
        self.layer.updateExtents()
        self.layerAdded.emit()
        self.updatelabel.emit(f"WKT完成！有效 {valid_count} 个，失败 {error_count} 个")