package routes

import (
	"bssapp-backend/auth"
	"bssapp-backend/db"
	"bssapp-backend/queries"
	"bssapp-backend/utils"
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/lib/pq"
)

type productPricingHistoryPGRow struct {
	ID         string  `json:"id"`
	Currency   string  `json:"currency"`
	LevelNo    int     `json:"level_no"`
	Price      float64 `json:"price"`
	UpdatedAt  string  `json:"updated_at"`
	SdprcGrpID int     `json:"sdprcgrp_id"`
}

type productPricingHistoryMSSQLRow struct {
	PriceListLineID string  `json:"price_list_line_id"`
	Currency        string  `json:"currency"`
	PriceGroupCode  string  `json:"price_group_code"`
	Price           float64 `json:"price"`
	ValidDate       string  `json:"valid_date"`
	ValidTime       string  `json:"valid_time"`
	LastUpdatedDate string  `json:"last_updated_date"`
	IsDisabled      bool    `json:"is_disabled"`
}

type productPricingHistoryResponse struct {
	ProductCode string                          `json:"product_code"`
	Postgres    []productPricingHistoryPGRow    `json:"postgres"`
	Mssql       []productPricingHistoryMSSQLRow `json:"mssql"`
}

func GetProductPricingHistoryHandler(pg *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		traceID := utils.TraceIDFromRequest(r)
		w.Header().Set("X-Trace-ID", traceID)

		claims, ok := auth.GetClaimsFromContext(r.Context())
		if !ok || claims == nil {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}

		productCode := strings.TrimSpace(mux.Vars(r)["code"])
		if productCode == "" {
			http.Error(w, "product code required", http.StatusBadRequest)
			return
		}

		ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 60*time.Second)
		defer cancel()

		// Load nebim price groups from PG mapping (18) + base groups (2).
		priceGroups := []string{"TM-USD", "TM-TRY"}
		if pg != nil {
			rows, err := pg.QueryContext(ctx, `
SELECT DISTINCT COALESCE(NULLIF(BTRIM(price_group_code), ''), '')
FROM mk_price_target_map_nebim
WHERE is_active = TRUE
`)
			if err == nil {
				for rows.Next() {
					var code string
					if err := rows.Scan(&code); err != nil {
						_ = rows.Close()
						break
					}
					code = strings.TrimSpace(code)
					if code != "" {
						priceGroups = append(priceGroups, code)
					}
				}
				_ = rows.Close()
			}
		}

		resp := productPricingHistoryResponse{
			ProductCode: productCode,
			Postgres:    []productPricingHistoryPGRow{},
			Mssql:       []productPricingHistoryMSSQLRow{},
		}

		// Postgres sdprc history.
		if pg != nil {
			pgRows, err := pg.QueryContext(ctx, `
SELECT
  sdprc.id::text,
  sdprc.crn,
  sdprc.sdprcgrp_id,
  COALESCE(sdprc.prc, 0)::float8,
  TO_CHAR(sdprc.zlins_dttm, 'YYYY-MM-DD HH24:MI:SS')
FROM sdprc
JOIN mmitem ON mmitem.id = sdprc.mmitem_id
WHERE mmitem.code = $1
  AND sdprc.crn IN ('USD','EUR','TRY')
  AND sdprc.sdprcgrp_id BETWEEN 1 AND 6
ORDER BY sdprc.zlins_dttm DESC
LIMIT 400;
`, productCode)
			if err == nil {
				for pgRows.Next() {
					var id, cur, at string
					var grp int
					var prc float64
					if err := pgRows.Scan(&id, &cur, &grp, &prc, &at); err != nil {
						_ = pgRows.Close()
						http.Error(w, "pg history scan error", http.StatusInternalServerError)
						return
					}
					resp.Postgres = append(resp.Postgres, productPricingHistoryPGRow{
						ID:         strings.TrimSpace(id),
						Currency:   strings.ToUpper(strings.TrimSpace(cur)),
						SdprcGrpID: grp,
						LevelNo:    grp,
						Price:      prc,
						UpdatedAt:  strings.TrimSpace(at),
					})
				}
				_ = pgRows.Close()
			}
		}

		// MSSQL trPriceListLine history (only relevant price groups).
		mssql := db.GetDB()
		if mssql != nil {
			// Build a safe "IN" via OR parameters.
			conds := make([]string, 0, len(priceGroups))
			args := make([]any, 0, len(priceGroups)+1)
			args = append(args, sql.Named("p1", productCode))
			for i, g := range priceGroups {
				name := fmt.Sprintf("g%d", i+1)
				conds = append(conds, "LTRIM(RTRIM(p.PriceGroupCode)) = @"+name)
				args = append(args, sql.Named(name, g))
			}
			wherePG := "1=0"
			if len(conds) > 0 {
				wherePG = "(" + strings.Join(conds, " OR ") + ")"
			}
			q := `
SELECT TOP (400)
  CONVERT(NVARCHAR(36), p.PriceListLineID) AS PriceListLineID,
  LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
  LTRIM(RTRIM(p.PriceGroupCode)) AS PriceGroupCode,
  CAST(p.Price AS FLOAT) AS Price,
  CONVERT(VARCHAR(10), p.ValidDate, 23) AS ValidDate,
  CONVERT(VARCHAR(8), p.ValidTime, 108) AS ValidTime,
  CONVERT(VARCHAR(19), p.LastUpdatedDate, 120) AS LastUpdatedDate,
  CAST(ISNULL(p.IsDisabled, 0) AS BIT) AS IsDisabled
FROM dbo.trPriceListLine p
WHERE p.ItemTypeCode = 1
  AND LTRIM(RTRIM(p.ItemCode)) = @p1
  AND ` + wherePG + `
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC;
`
			rows, err := mssql.QueryContext(ctx, q, args...)
			if err == nil {
				for rows.Next() {
					var id, cur, grp, vd, vt, lud string
					var prc float64
					var disabled bool
					if err := rows.Scan(&id, &cur, &grp, &prc, &vd, &vt, &lud, &disabled); err != nil {
						_ = rows.Close()
						http.Error(w, "mssql history scan error", http.StatusInternalServerError)
						return
					}
					resp.Mssql = append(resp.Mssql, productPricingHistoryMSSQLRow{
						PriceListLineID: strings.TrimSpace(id),
						Currency:        strings.ToUpper(strings.TrimSpace(cur)),
						PriceGroupCode:  strings.TrimSpace(grp),
						Price:           prc,
						ValidDate:       strings.TrimSpace(vd),
						ValidTime:       strings.TrimSpace(vt),
						LastUpdatedDate: strings.TrimSpace(lud),
						IsDisabled:      disabled,
					})
				}
				_ = rows.Close()
			}
		}

		_ = json.NewEncoder(w).Encode(resp)
	}
}

type deleteLatestPriceHistoryRequest struct {
	DeletePostgres bool   `json:"delete_postgres"`
	DeleteMssql    bool   `json:"delete_mssql"`
	Currency       string `json:"currency"` // USD/EUR/TRY
	LevelNo        int    `json:"level_no"` // 1..6 (tier); for base use 0
	IsBase         bool   `json:"is_base"`
	PriceGroupCode string `json:"price_group_code"` // optional override for MSSQL deletes
}

func PostDeleteLatestProductPriceHistoryHandler(pg *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		traceID := utils.TraceIDFromRequest(r)
		w.Header().Set("X-Trace-ID", traceID)

		claims, ok := auth.GetClaimsFromContext(r.Context())
		if !ok || claims == nil {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}

		productCode := strings.TrimSpace(mux.Vars(r)["code"])
		if productCode == "" {
			http.Error(w, "product code required", http.StatusBadRequest)
			return
		}

		var req deleteLatestPriceHistoryRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, "invalid payload", http.StatusBadRequest)
			return
		}
		if !req.DeletePostgres && !req.DeleteMssql {
			req.DeletePostgres = true
			req.DeleteMssql = true
		}

		cur := strings.ToUpper(strings.TrimSpace(req.Currency))
		if cur != "USD" && cur != "EUR" && cur != "TRY" {
			http.Error(w, "invalid currency", http.StatusBadRequest)
			return
		}
		if !req.IsBase && req.DeletePostgres && (req.LevelNo < 1 || req.LevelNo > 6) {
			http.Error(w, "invalid level_no", http.StatusBadRequest)
			return
		}

		ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 60*time.Second)
		defer cancel()

		// PG delete (sdprc).
		deletedPG := int64(0)
		if req.DeletePostgres && !req.IsBase && pg != nil {
			tx, err := pg.BeginTx(ctx, nil)
			if err != nil {
				http.Error(w, "pg tx error", http.StatusInternalServerError)
				return
			}
			defer tx.Rollback()

			var mmItemID int64
			if err := tx.QueryRowContext(ctx, `SELECT id FROM mmitem WHERE code=$1`, productCode).Scan(&mmItemID); err != nil {
				http.Error(w, "pg product not found", http.StatusNotFound)
				return
			}
			grp := req.LevelNo
			// Delete latest row for that currency+level.
			res, err := tx.ExecContext(ctx, `
DELETE FROM sdprc
WHERE id = (
  SELECT id
  FROM sdprc
  WHERE mmitem_id=$1 AND crn=$2 AND sdprcgrp_id=$3
  ORDER BY zlins_dttm DESC
  LIMIT 1
);
`, mmItemID, cur, grp)
			if err != nil {
				http.Error(w, "pg delete error", http.StatusInternalServerError)
				return
			}
			deletedPG, _ = res.RowsAffected()

			// enqueue delta recompute for this product to keep derived currencies consistent
			_, _ = queries.EnqueuePriceRecalc(ctx, tx, []string{productCode}, "history_delete")

			if err := tx.Commit(); err != nil {
				http.Error(w, "pg commit error", http.StatusInternalServerError)
				return
			}
		}

		// MSSQL delete (trPriceListLine).
		deletedMSSQL := int64(0)
		if req.DeleteMssql {
			mssql := db.GetDB()
			if mssql == nil {
				http.Error(w, "mssql not connected", http.StatusInternalServerError)
				return
			}
			tx, err := mssql.BeginTx(ctx, nil)
			if err != nil {
				http.Error(w, "mssql tx error", http.StatusInternalServerError)
				return
			}
			defer tx.Rollback()

			priceGroup := strings.TrimSpace(req.PriceGroupCode)
			if req.IsBase {
				if cur == "USD" {
					priceGroup = "TM-USD"
				} else if cur == "TRY" {
					priceGroup = "TM-TRY"
				} else {
					http.Error(w, "base only supports USD/TRY", http.StatusBadRequest)
					return
				}
			} else if priceGroup == "" && pg != nil {
				_ = pg.QueryRowContext(ctx, `
SELECT COALESCE(NULLIF(BTRIM(price_group_code), ''), '')
FROM mk_price_target_map_nebim
WHERE is_active=TRUE AND currency=$1 AND level_no=$2
`, cur, req.LevelNo).Scan(&priceGroup)
			}
			priceGroup = strings.TrimSpace(priceGroup)
			if priceGroup == "" {
				http.Error(w, "missing price group mapping", http.StatusBadRequest)
				return
			}

			res, err := tx.ExecContext(ctx, `
;WITH latest AS (
  SELECT TOP (1) p.PriceListLineID
  FROM dbo.trPriceListLine p WITH(UPDLOCK, ROWLOCK)
  WHERE p.ItemTypeCode=1
    AND LTRIM(RTRIM(p.ItemCode))=@p1
    AND LTRIM(RTRIM(p.DocCurrencyCode))=@p2
    AND LTRIM(RTRIM(p.PriceGroupCode))=@p3
    AND ISNULL(p.IsDisabled, 0)=0
  ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
)
DELETE FROM dbo.trPriceListLine
WHERE PriceListLineID IN (SELECT PriceListLineID FROM latest);
`, sql.Named("p1", productCode), sql.Named("p2", cur), sql.Named("p3", priceGroup))
			if err != nil {
				http.Error(w, "mssql delete error", http.StatusInternalServerError)
				return
			}
			deletedMSSQL, _ = res.RowsAffected()

			if err := tx.Commit(); err != nil {
				http.Error(w, "mssql commit error", http.StatusInternalServerError)
				return
			}
		}

		_ = json.NewEncoder(w).Encode(map[string]any{
			"success":       true,
			"product_code":  productCode,
			"deleted_pg":    deletedPG,
			"deleted_mssql": deletedMSSQL,
			"actor_user":    claims.Username,
			"actor_user_id": claims.ID,
		})
	}
}

type deleteSelectedPriceHistoryRequest struct {
	PGIDs    []string `json:"pg_ids"`    // sdprc.id (uuid)
	MSSQLIDs []string `json:"mssql_ids"` // trPriceListLine.PriceListLineID (uuid)
}

func PostDeleteSelectedProductPriceHistoryHandler(pg *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		traceID := utils.TraceIDFromRequest(r)
		w.Header().Set("X-Trace-ID", traceID)

		claims, ok := auth.GetClaimsFromContext(r.Context())
		if !ok || claims == nil {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}

		productCode := strings.TrimSpace(mux.Vars(r)["code"])
		if productCode == "" {
			http.Error(w, "product code required", http.StatusBadRequest)
			return
		}

		var req deleteSelectedPriceHistoryRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, "invalid payload", http.StatusBadRequest)
			return
		}
		// normalize ids
		pgIDs := make([]string, 0, len(req.PGIDs))
		for _, x := range req.PGIDs {
			s := strings.TrimSpace(x)
			if s != "" {
				pgIDs = append(pgIDs, s)
			}
		}
		msIDs := make([]string, 0, len(req.MSSQLIDs))
		for _, x := range req.MSSQLIDs {
			s := strings.TrimSpace(x)
			if s != "" {
				msIDs = append(msIDs, s)
			}
		}
		if len(pgIDs) == 0 && len(msIDs) == 0 {
			http.Error(w, "no ids selected", http.StatusBadRequest)
			return
		}

		ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 60*time.Second)
		defer cancel()

		deletedPG := int64(0)
		if len(pgIDs) > 0 && pg != nil {
			tx, err := pg.BeginTx(ctx, nil)
			if err != nil {
				http.Error(w, "pg tx error", http.StatusInternalServerError)
				return
			}
			defer tx.Rollback()

			// Resolve product id to constrain deletes to the given productCode.
			var mmItemID int64
			if err := tx.QueryRowContext(ctx, `SELECT id FROM mmitem WHERE code=$1`, productCode).Scan(&mmItemID); err != nil {
				http.Error(w, "pg product not found", http.StatusNotFound)
				return
			}

			// Delete only rows matching mmitem_id + id list.
			res, err := tx.ExecContext(ctx, `
DELETE FROM sdprc
WHERE mmitem_id = $1
  AND id = ANY($2::uuid[]);
`, mmItemID, pq.Array(pgIDs))
			if err != nil {
				http.Error(w, "pg delete error", http.StatusInternalServerError)
				return
			}
			deletedPG, _ = res.RowsAffected()

			_, _ = queries.EnqueuePriceRecalc(ctx, tx, []string{productCode}, "history_delete_selected")

			if err := tx.Commit(); err != nil {
				http.Error(w, "pg commit error", http.StatusInternalServerError)
				return
			}
		}

		deletedMSSQL := int64(0)
		if len(msIDs) > 0 {
			mssql := db.GetDB()
			if mssql == nil {
				http.Error(w, "mssql not connected", http.StatusInternalServerError)
				return
			}
			tx, err := mssql.BeginTx(ctx, nil)
			if err != nil {
				http.Error(w, "mssql tx error", http.StatusInternalServerError)
				return
			}
			defer tx.Rollback()

			// Build a safe IN-list via named parameters.
			placeholders := make([]string, 0, len(msIDs))
			args := make([]any, 0, len(msIDs)+1)
			args = append(args, sql.Named("p1", productCode))
			for i, id := range msIDs {
				name := fmt.Sprintf("id%d", i+1)
				placeholders = append(placeholders, "@"+name)
				args = append(args, sql.Named(name, id))
			}

			q := `
DELETE FROM dbo.trPriceListLine
WHERE ItemTypeCode = 1
  AND LTRIM(RTRIM(ItemCode)) = @p1
  AND PriceListLineID IN (` + strings.Join(placeholders, ",") + `);
`
			res, err := tx.ExecContext(ctx, q, args...)
			if err != nil {
				http.Error(w, "mssql delete error", http.StatusInternalServerError)
				return
			}
			deletedMSSQL, _ = res.RowsAffected()

			if err := tx.Commit(); err != nil {
				http.Error(w, "mssql commit error", http.StatusInternalServerError)
				return
			}
		}

		_ = json.NewEncoder(w).Encode(map[string]any{
			"success":       true,
			"product_code":  productCode,
			"deleted_pg":    deletedPG,
			"deleted_mssql": deletedMSSQL,
			"actor_user":    claims.Username,
			"actor_user_id": claims.ID,
		})
	}
}
