package applogic

import (
	"bytes"
	"encoding/csv"
	"errors"
	"fmt"
	"io"
	"path/filepath"
	"strconv"
	"strings"

	"aijianzhan/platform/internal/authx"
	"aijianzhan/platform/internal/meta"
	"aijianzhan/platform/internal/types"

	"github.com/lib/pq"
	"github.com/xuri/excelize/v2"
)

// ImportRows accepts .xlsx (preferred) or .csv.
func (l *CrudLogic) ImportRows(slug, resource, filename string, r io.Reader) (*types.ImportResp, error) {
	tenantID := authx.TenantID(l.ctx)
	userID := authx.UserID(l.ctx)
	ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource)
	if err != nil {
		return nil, err
	}
	// Z9f：读路径内存兜底（与 Z9e 扫库双保险）
	ensureImportOpInMemory(ref)
	if !hasOp(ref, "import") {
		return nil, fmt.Errorf("蓝图未开启 import（operations 缺 import）。请管理员执行「一键开启全部业务表导入」或再发布模块；与账号权限 row.import 无关")
	}

	ext := strings.ToLower(filepath.Ext(filename))
	raw, err := io.ReadAll(r)
	if err != nil {
		return nil, err
	}
	if len(raw) == 0 {
		return nil, fmt.Errorf("empty file")
	}

	var rows [][]string
	switch {
	case ext == ".xlsx" || ext == ".xlsm" || isZipOOXML(raw):
		rows, err = readExcelRows(raw)
	case ext == ".csv" || ext == ".txt" || looksLikeCSV(raw):
		rows, err = readCSVRows(raw)
	default:
		// try excel then csv
		rows, err = readExcelRows(raw)
		if err != nil {
			rows, err = readCSVRows(raw)
		}
	}
	if err != nil {
		return nil, fmt.Errorf("parse file: %w", err)
	}
	if len(rows) < 1 {
		return nil, fmt.Errorf("no header row")
	}

	headers := make([]string, len(rows[0]))
	for i, h := range rows[0] {
		headers[i] = strings.TrimSpace(h)
	}

	resp := &types.ImportResp{Errors: []string{}}
	for rowNum := 1; rowNum < len(rows); rowNum++ {
		rec := rows[rowNum]
		if rowEmpty(rec) {
			continue
		}
		body := map[string]any{}
		for i, h := range headers {
			if i >= len(rec) || h == "" {
				continue
			}
			fname := mapHeaderToField(ref, h)
			if fname == "" || fname == ref.Entity.PrimaryKey {
				continue
			}
			body[fname] = coerceValue(ref, fname, strings.TrimSpace(rec[i]))
		}
		if len(body) == 0 {
			resp.Skipped++
			continue
		}
		if _, err := l.svcCtx.CRUD.Create(l.ctx, ref, tenantID, userID, body); err != nil {
			resp.Skipped++
			if len(resp.Errors) < 100 {
				msg := err.Error()
				if isUndefinedColumnErr(err) {
					msg = fmt.Sprintf("%v（蓝图字段与库表不一致，请重新发布模块以自动补列，或缩小 Excel 表头至库已有列）", err)
				}
				resp.Errors = append(resp.Errors, fmt.Sprintf("row %d: %s", rowNum+1, msg))
			}
			continue
		}
		resp.Inserted++
	}
	return resp, nil
}

// ImportCSV kept for callers; prefers CSV parsing.
func (l *CrudLogic) ImportCSV(slug, resource string, r io.Reader) (*types.ImportResp, error) {
	return l.ImportRows(slug, resource, "import.csv", r)
}

// ExportExcel writes .xlsx workbook.
func (l *CrudLogic) ExportExcel(slug, resource string) ([]byte, string, error) {
	tenantID := authx.TenantID(l.ctx)
	ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource)
	if err != nil {
		return nil, "", err
	}
	ensureImportOpInMemory(ref)
	if !hasOp(ref, "export") {
		return nil, "", fmt.Errorf("蓝图未开启 export（operations 缺 export）。请管理员执行「一键开启全部业务表导入」或再发布模块")
	}
	items, _, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "")
	if err != nil {
		return nil, "", err
	}
	headers := make([]string, 0, len(ref.Entity.Fields))
	for _, f := range ref.Entity.Fields {
		headers = append(headers, f.Name)
	}

	f := excelize.NewFile()
	sheet := f.GetSheetName(0)
	for i, h := range headers {
		cell, _ := excelize.CoordinatesToCellName(i+1, 1)
		_ = f.SetCellValue(sheet, cell, h)
	}
	for ri, item := range items {
		for ci, h := range headers {
			cell, _ := excelize.CoordinatesToCellName(ci+1, ri+2)
			if v, ok := item[h]; ok && v != nil {
				_ = f.SetCellValue(sheet, cell, v)
			}
		}
	}
	buf, err := f.WriteToBuffer()
	if err != nil {
		return nil, "", err
	}
	return buf.Bytes(), resource + ".xlsx", nil
}

// ExportCSV kept for format=csv.
func (l *CrudLogic) ExportCSV(slug, resource string) ([]byte, error) {
	tenantID := authx.TenantID(l.ctx)
	ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource)
	if err != nil {
		return nil, err
	}
	ensureImportOpInMemory(ref)
	if !hasOp(ref, "export") {
		return nil, fmt.Errorf("蓝图未开启 export（operations 缺 export）。请管理员执行「一键开启全部业务表导入」或再发布模块")
	}
	items, _, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "")
	if err != nil {
		return nil, err
	}
	headers := make([]string, 0, len(ref.Entity.Fields))
	for _, f := range ref.Entity.Fields {
		headers = append(headers, f.Name)
	}
	buf := &bytes.Buffer{}
	w := csv.NewWriter(buf)
	_ = w.Write(headers)
	for _, item := range items {
		row := make([]string, len(headers))
		for i, h := range headers {
			if v, ok := item[h]; ok && v != nil {
				row[i] = fmt.Sprint(v)
			}
		}
		_ = w.Write(row)
	}
	w.Flush()
	return buf.Bytes(), w.Error()
}

func readExcelRows(raw []byte) ([][]string, error) {
	f, err := excelize.OpenReader(bytes.NewReader(raw))
	if err != nil {
		return nil, err
	}
	defer func() { _ = f.Close() }()
	sheets := f.GetSheetList()
	if len(sheets) == 0 {
		return nil, fmt.Errorf("excel has no sheets")
	}
	// 优先业务数据页，跳过说明/超限汇总
	prefer := ""
	for _, name := range sheets {
		n := strings.ToLower(name)
		if strings.Contains(n, "settlement") || (strings.Contains(name, "测点") && !strings.Contains(name, "超限") && !strings.Contains(name, "说明")) {
			prefer = name
			break
		}
	}
	if prefer == "" {
		for _, name := range sheets {
			if !strings.Contains(name, "说明") && !strings.Contains(name, "超限") {
				prefer = name
				break
			}
		}
	}
	if prefer == "" {
		prefer = sheets[0]
	}
	return f.GetRows(prefer)
}

func readCSVRows(raw []byte) ([][]string, error) {
	cr := csv.NewReader(bytes.NewReader(raw))
	cr.FieldsPerRecord = -1
	return cr.ReadAll()
}

func isZipOOXML(raw []byte) bool {
	// xlsx is a zip archive
	return len(raw) >= 4 && raw[0] == 'P' && raw[1] == 'K' && raw[2] == 3 && raw[3] == 4
}

func looksLikeCSV(raw []byte) bool {
	sample := raw
	if len(sample) > 512 {
		sample = sample[:512]
	}
	s := string(sample)
	return strings.Contains(s, ",") || strings.Contains(s, "\t") || strings.Contains(s, ";")
}

func rowEmpty(rec []string) bool {
	for _, c := range rec {
		if strings.TrimSpace(c) != "" {
			return false
		}
	}
	return true
}

func hasOp(ref *meta.ResourceRef, op string) bool {
	if ref == nil {
		return false
	}
	for _, o := range ref.Resource.Operations {
		if o == op {
			return true
		}
	}
	return false
}

// ensureImportOpInMemory Z9f：对已加载蓝图做与 EnsureDefaultImportExport 相同的内存补齐，并刷新 ref.Resource。
func ensureImportOpInMemory(ref *meta.ResourceRef) {
	if ref == nil || ref.App == nil || ref.App.Blueprint == nil {
		return
	}
	ref.App.Blueprint.EnsureDefaultImportExport()
	want := strings.TrimSpace(ref.Resource.Path)
	if len(want) > 0 && want[0] == '/' {
		want = want[1:]
	}
	for _, r := range ref.App.Blueprint.Apis.Resources {
		path := r.Path
		if len(path) > 0 && path[0] == '/' {
			path = path[1:]
		}
		if path == want {
			ref.Resource = r
			return
		}
	}
}

func isUndefinedColumnErr(err error) bool {
	var pqErr *pq.Error
	if errors.As(err, &pqErr) && pqErr.Code == "42703" {
		return true
	}
	msg := err.Error()
	return strings.Contains(msg, "42703") || strings.Contains(msg, "字段不存在") || strings.Contains(msg, "does not exist")
}

func mapHeaderToField(ref *meta.ResourceRef, header string) string {
	h := strings.TrimSpace(header)
	if h == "" {
		return ""
	}
	norm := normalizeHeader(h)
	aliases := map[string]string{
		"编号dk": "dkilo", "测点dk": "dkilo", "dkilo": "dkilo",
		"里程": "chainage", "chainage": "chainage",
		"测点编号": "point_code", "测点": "point_code", "point_code": "point_code",
		"断面类型": "section_type", "section_type": "section_type",
		"工点": "worksite", "worksite": "worksite",
		"cjl数值": "cjl_value", "观测值": "cjl_value", "观测值mm": "cjl_value", "cjl_value": "cjl_value",
		"cjl颜色": "cjl_color", "观测色": "cjl_color", "cjl_color": "cjl_color",
		"设计沉降": "design_settlement_mm", "设计总沉降量mm": "design_settlement_mm", "design_settlement_mm": "design_settlement_mm",
		"累积沉降": "cum_settlement_mm", "累积沉降量mm": "cum_settlement_mm", "cum_settlement_mm": "cum_settlement_mm",
		"预测沉降": "pred_settlement_mm", "预测沉降mm": "pred_settlement_mm", "pred_settlement_mm": "pred_settlement_mm",
		"超限量": "exceed_mm", "超限量mm": "exceed_mm", "exceed_mm": "exceed_mm",
		"超限累计天数": "exceed_days", "累计天数天": "exceed_days", "exceed_days": "exceed_days",
		"监督天": "supervise_days", "监督周期天": "supervise_days", "supervise_days": "supervise_days",
		"超期天数": "overdue_days", "overdue_days": "overdue_days",
		"前一日占比": "before_day", "before_day": "before_day", "beforeday": "before_day",
		"后一日占比": "next_day", "next_day": "next_day", "nextday": "next_day",
		"频率": "frequency", "frequency": "frequency",
		"cljd标识": "workinfo_kilo", "workinfo_kilo": "workinfo_kilo",
		"状态": "status", "status": "status",
		"里程米": "mileage_m", "mileage_m": "mileage_m",
	}
	if a, ok := aliases[norm]; ok {
		h = a
		norm = a
	}
	for _, f := range ref.Entity.Fields {
		if f.Name == h || f.Label == header || normalizeHeader(f.Label) == norm || f.Name == norm {
			return f.Name
		}
	}
	return ""
}

func normalizeHeader(s string) string {
	s = strings.TrimSpace(strings.ToLower(s))
	repl := strings.NewReplacer(" ", "", "_", "", "-", "", "(", "", ")", "", "（", "", "）", "", ".", "")
	return repl.Replace(s)
}

func coerceValue(ref *meta.ResourceRef, fieldName, raw string) any {
	if raw == "" {
		return nil
	}
	for _, f := range ref.Entity.Fields {
		if f.Name != fieldName {
			continue
		}
		switch f.Type {
		case "int", "bigint":
			n, err := strconv.ParseInt(raw, 10, 64)
			if err == nil {
				return n
			}
		case "decimal":
			n, err := strconv.ParseFloat(raw, 64)
			if err == nil {
				return n
			}
		case "boolean":
			return raw == "1" || strings.EqualFold(raw, "true") || raw == "是"
		}
		return raw
	}
	return raw
}
