package handlers

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"
)

// TenderHandlers handles tender upload and requirement extraction
type TenderHandlers struct {
	pool     *pgxpool.Pool
	controls *PaymentControlLibrary
}

// TenderAnalysis represents a tender document analysis
type TenderAnalysis struct {
	ID               uuid.UUID          `json:"id"`
	TenantID         uuid.UUID          `json:"tenant_id"`
	FileName         string             `json:"file_name"`
	FileSize         int64              `json:"file_size"`
	ProjectName      string             `json:"project_name"`
	CustomerName     string             `json:"customer_name,omitempty"`
	Status           string             `json:"status"` // uploaded, extracting, extracted, matched, completed
	Requirements     []ExtractedReq     `json:"requirements,omitempty"`
	MatchResults     []MatchResult      `json:"match_results,omitempty"`
	TotalRequirements int               `json:"total_requirements"`
	MatchedCount     int                `json:"matched_count"`
	UnmatchedCount   int                `json:"unmatched_count"`
	PartialCount     int                `json:"partial_count"`
	CreatedAt        time.Time          `json:"created_at"`
	UpdatedAt        time.Time          `json:"updated_at"`
}

// ExtractedReq represents a single requirement extracted from a tender document
type ExtractedReq struct {
	ReqID          string   `json:"req_id"`
	Text           string   `json:"text"`
	SourcePage     int      `json:"source_page,omitempty"`
	SourceSection  string   `json:"source_section,omitempty"`
	ObligationLevel string  `json:"obligation_level"` // MUST, SHALL, SHOULD, MAY
	TechnicalDomain string  `json:"technical_domain"` // crypto, logging, payment_flow, etc.
	CheckTarget    string   `json:"check_target"`     // code, system, config, process, certificate
	Confidence     float64  `json:"confidence"`
}

// MatchResult represents the matching of a requirement to controls
type MatchResult struct {
	ReqID          string          `json:"req_id"`
	ReqText        string          `json:"req_text"`
	ObligationLevel string         `json:"obligation_level"`
	MatchedControls []ControlMatch `json:"matched_controls"`
	Verdict        string          `json:"verdict"` // matched, partial, unmatched
	GapDescription string          `json:"gap_description,omitempty"`
}

// ControlMatch represents a single control match for a requirement
type ControlMatch struct {
	ControlID  string  `json:"control_id"`
	Title      string  `json:"title"`
	Relevance  float64 `json:"relevance"` // 0-1
	CheckTarget string `json:"check_target"`
}

// NewTenderHandlers creates tender handlers
func NewTenderHandlers(pool *pgxpool.Pool, controls *PaymentControlLibrary) *TenderHandlers {
	return &TenderHandlers{pool: pool, controls: controls}
}

// Upload handles tender document upload
func (h *TenderHandlers) Upload(c *gin.Context) {
	tenantID, _ := uuid.Parse(c.GetHeader("X-Tenant-ID"))
	if tenantID == uuid.Nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
		return
	}

	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "file required"})
		return
	}
	defer file.Close()

	projectName := c.PostForm("project_name")
	if projectName == "" {
		projectName = header.Filename
	}
	customerName := c.PostForm("customer_name")

	// Read file content
	content, err := io.ReadAll(file)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read file"})
		return
	}

	// Store analysis record
	analysisID := uuid.New()
	now := time.Now()

	_, err = h.pool.Exec(c.Request.Context(), `
		INSERT INTO tender_analyses (
			id, tenant_id, file_name, file_size, file_content,
			project_name, customer_name, status, created_at, updated_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7, 'uploaded', $8, $9)`,
		analysisID, tenantID, header.Filename, header.Size, content,
		projectName, customerName, now, now,
	)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store: " + err.Error()})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":           analysisID,
		"file_name":    header.Filename,
		"file_size":    header.Size,
		"project_name": projectName,
		"status":       "uploaded",
		"message":      "Dokument hochgeladen. Starte Analyse mit POST /extract.",
	})
}

// Extract extracts requirements from an uploaded tender document using LLM
func (h *TenderHandlers) Extract(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
		return
	}

	// Get file content
	var fileContent []byte
	var fileName string
	err = h.pool.QueryRow(c.Request.Context(), `
		SELECT file_content, file_name FROM tender_analyses WHERE id = $1`, id,
	).Scan(&fileContent, &fileName)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "analysis not found"})
		return
	}

	// Update status
	h.pool.Exec(c.Request.Context(), `
		UPDATE tender_analyses SET status = 'extracting', updated_at = NOW() WHERE id = $1`, id)

	// Extract text (simple: treat as text for now, PDF extraction would use embedding-service)
	text := string(fileContent)

	// Use LLM to extract requirements
	requirements := h.extractRequirementsWithLLM(c.Request.Context(), text)

	// Store results
	reqJSON, _ := json.Marshal(requirements)
	h.pool.Exec(c.Request.Context(), `
		UPDATE tender_analyses SET
			status = 'extracted',
			requirements = $2,
			total_requirements = $3,
			updated_at = NOW()
		WHERE id = $1`, id, reqJSON, len(requirements))

	c.JSON(http.StatusOK, gin.H{
		"id":            id,
		"status":        "extracted",
		"requirements":  requirements,
		"total":         len(requirements),
	})
}

// Match matches extracted requirements against the control library
func (h *TenderHandlers) Match(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
		return
	}

	// Get requirements
	var reqJSON json.RawMessage
	err = h.pool.QueryRow(c.Request.Context(), `
		SELECT requirements FROM tender_analyses WHERE id = $1`, id,
	).Scan(&reqJSON)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "analysis not found"})
		return
	}

	var requirements []ExtractedReq
	json.Unmarshal(reqJSON, &requirements)

	// Match each requirement against controls
	var results []MatchResult
	matched, unmatched, partial := 0, 0, 0

	for _, req := range requirements {
		matches := h.findMatchingControls(req)
		result := MatchResult{
			ReqID:           req.ReqID,
			ReqText:         req.Text,
			ObligationLevel: req.ObligationLevel,
			MatchedControls: matches,
		}

		if len(matches) == 0 {
			result.Verdict = "unmatched"
			result.GapDescription = "Kein passender Control gefunden — manueller Review erforderlich"
			unmatched++
		} else if matches[0].Relevance >= 0.7 {
			result.Verdict = "matched"
			matched++
		} else {
			result.Verdict = "partial"
			result.GapDescription = "Teilweise Abdeckung — Control deckt Anforderung nicht vollstaendig ab"
			partial++
		}

		results = append(results, result)
	}

	// Store results
	resultsJSON, _ := json.Marshal(results)
	h.pool.Exec(c.Request.Context(), `
		UPDATE tender_analyses SET
			status = 'matched',
			match_results = $2,
			matched_count = $3,
			unmatched_count = $4,
			partial_count = $5,
			updated_at = NOW()
		WHERE id = $1`, id, resultsJSON, matched, unmatched, partial)

	c.JSON(http.StatusOK, gin.H{
		"id":        id,
		"status":    "matched",
		"results":   results,
		"matched":   matched,
		"unmatched": unmatched,
		"partial":   partial,
		"total":     len(requirements),
	})
}

// ListAnalyses lists all tender analyses for a tenant
func (h *TenderHandlers) ListAnalyses(c *gin.Context) {
	tenantID, _ := uuid.Parse(c.GetHeader("X-Tenant-ID"))
	if tenantID == uuid.Nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
		return
	}

	rows, err := h.pool.Query(c.Request.Context(), `
		SELECT id, tenant_id, file_name, file_size, project_name, customer_name,
			status, total_requirements, matched_count, unmatched_count, partial_count,
			created_at, updated_at
		FROM tender_analyses
		WHERE tenant_id = $1
		ORDER BY created_at DESC`, tenantID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	var analyses []TenderAnalysis
	for rows.Next() {
		var a TenderAnalysis
		rows.Scan(&a.ID, &a.TenantID, &a.FileName, &a.FileSize, &a.ProjectName, &a.CustomerName,
			&a.Status, &a.TotalRequirements, &a.MatchedCount, &a.UnmatchedCount, &a.PartialCount,
			&a.CreatedAt, &a.UpdatedAt)
		analyses = append(analyses, a)
	}
	if analyses == nil {
		analyses = []TenderAnalysis{}
	}

	c.JSON(http.StatusOK, gin.H{"analyses": analyses, "total": len(analyses)})
}

// GetAnalysis returns a single analysis with all details
func (h *TenderHandlers) GetAnalysis(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
		return
	}

	var a TenderAnalysis
	var reqJSON, matchJSON json.RawMessage
	err = h.pool.QueryRow(c.Request.Context(), `
		SELECT id, tenant_id, file_name, file_size, project_name, customer_name,
			status, requirements, match_results,
			total_requirements, matched_count, unmatched_count, partial_count,
			created_at, updated_at
		FROM tender_analyses WHERE id = $1`, id).Scan(
		&a.ID, &a.TenantID, &a.FileName, &a.FileSize, &a.ProjectName, &a.CustomerName,
		&a.Status, &reqJSON, &matchJSON,
		&a.TotalRequirements, &a.MatchedCount, &a.UnmatchedCount, &a.PartialCount,
		&a.CreatedAt, &a.UpdatedAt)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}

	if reqJSON != nil {
		json.Unmarshal(reqJSON, &a.Requirements)
	}
	if matchJSON != nil {
		json.Unmarshal(matchJSON, &a.MatchResults)
	}

	c.JSON(http.StatusOK, a)
}

// --- Internal helpers ---

func (h *TenderHandlers) extractRequirementsWithLLM(ctx context.Context, text string) []ExtractedReq {
	// Try Anthropic API for requirement extraction
	apiKey := os.Getenv("ANTHROPIC_API_KEY")
	if apiKey == "" {
		// Fallback: simple keyword-based extraction
		return h.extractRequirementsKeyword(text)
	}

	prompt := fmt.Sprintf(`Analysiere das folgende Ausschreibungsdokument und extrahiere alle technischen Anforderungen.

Fuer jede Anforderung gib zurueck:
- req_id: fortlaufende ID (REQ-001, REQ-002, ...)
- text: die Anforderung als kurzer Satz
- obligation_level: MUST, SHALL, SHOULD oder MAY
- technical_domain: eines von: payment_flow, logging, crypto, api_security, terminal_comm, firmware, reporting, access_control, error_handling, build_deploy
- check_target: eines von: code, system, config, process, certificate

Antworte NUR mit JSON Array. Keine Erklaerung.

Dokument:
%s`, text[:min(len(text), 15000)])

	body := map[string]interface{}{
		"model":      "claude-haiku-4-5-20251001",
		"max_tokens": 4096,
		"messages":   []map[string]string{{"role": "user", "content": prompt}},
	}
	bodyJSON, _ := json.Marshal(body)

	req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.anthropic.com/v1/messages", strings.NewReader(string(bodyJSON)))
	req.Header.Set("x-api-key", apiKey)
	req.Header.Set("anthropic-version", "2023-06-01")
	req.Header.Set("content-type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil || resp.StatusCode != 200 {
		return h.extractRequirementsKeyword(text)
	}
	defer resp.Body.Close()

	var result struct {
		Content []struct {
			Text string `json:"text"`
		} `json:"content"`
	}
	json.NewDecoder(resp.Body).Decode(&result)

	if len(result.Content) == 0 {
		return h.extractRequirementsKeyword(text)
	}

	// Parse LLM response
	responseText := result.Content[0].Text
	// Find JSON array in response
	start := strings.Index(responseText, "[")
	end := strings.LastIndex(responseText, "]")
	if start < 0 || end < 0 {
		return h.extractRequirementsKeyword(text)
	}

	var reqs []ExtractedReq
	if err := json.Unmarshal([]byte(responseText[start:end+1]), &reqs); err != nil {
		return h.extractRequirementsKeyword(text)
	}

	// Set confidence for LLM-extracted requirements
	for i := range reqs {
		reqs[i].Confidence = 0.8
	}

	return reqs
}

func (h *TenderHandlers) extractRequirementsKeyword(text string) []ExtractedReq {
	// Simple keyword-based extraction as fallback
	keywords := map[string]string{
		"muss":           "MUST",
		"muessen":        "MUST",
		"ist sicherzustellen": "MUST",
		"soll":           "SHOULD",
		"sollte":         "SHOULD",
		"kann":           "MAY",
		"wird gefordert": "MUST",
		"nachzuweisen":   "MUST",
		"zertifiziert":   "MUST",
	}

	var reqs []ExtractedReq
	lines := strings.Split(text, "\n")
	reqNum := 1

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if len(line) < 20 || len(line) > 500 {
			continue
		}

		for keyword, level := range keywords {
			if strings.Contains(strings.ToLower(line), keyword) {
				reqs = append(reqs, ExtractedReq{
					ReqID:           fmt.Sprintf("REQ-%03d", reqNum),
					Text:            line,
					ObligationLevel: level,
					TechnicalDomain: inferDomain(line),
					CheckTarget:     inferCheckTarget(line),
					Confidence:      0.5,
				})
				reqNum++
				break
			}
		}
	}

	return reqs
}

func (h *TenderHandlers) findMatchingControls(req ExtractedReq) []ControlMatch {
	var matches []ControlMatch

	reqLower := strings.ToLower(req.Text + " " + req.TechnicalDomain)

	for _, ctrl := range h.controls.Controls {
		titleLower := strings.ToLower(ctrl.Title + " " + ctrl.Objective)
		relevance := calculateRelevance(reqLower, titleLower, req.TechnicalDomain, ctrl.Domain)

		if relevance > 0.3 {
			matches = append(matches, ControlMatch{
				ControlID:   ctrl.ControlID,
				Title:       ctrl.Title,
				Relevance:   relevance,
				CheckTarget: ctrl.CheckTarget,
			})
		}
	}

	// Sort by relevance (simple bubble sort for small lists)
	for i := 0; i < len(matches); i++ {
		for j := i + 1; j < len(matches); j++ {
			if matches[j].Relevance > matches[i].Relevance {
				matches[i], matches[j] = matches[j], matches[i]
			}
		}
	}

	// Return top 5
	if len(matches) > 5 {
		matches = matches[:5]
	}

	return matches
}

func calculateRelevance(reqText, ctrlText, reqDomain, ctrlDomain string) float64 {
	score := 0.0

	// Domain match bonus
	domainMap := map[string]string{
		"payment_flow":   "PAY",
		"logging":        "LOG",
		"crypto":         "CRYPTO",
		"api_security":   "API",
		"terminal_comm":  "TERM",
		"firmware":       "FW",
		"reporting":      "REP",
		"access_control": "ACC",
		"error_handling":  "ERR",
		"build_deploy":   "BLD",
	}

	if mapped, ok := domainMap[reqDomain]; ok && mapped == ctrlDomain {
		score += 0.4
	}

	// Keyword overlap
	reqWords := strings.Fields(reqText)
	for _, word := range reqWords {
		if len(word) > 3 && strings.Contains(ctrlText, word) {
			score += 0.1
		}
	}

	if score > 1.0 {
		score = 1.0
	}
	return score
}

func inferDomain(text string) string {
	textLower := strings.ToLower(text)
	domainKeywords := map[string][]string{
		"payment_flow":   {"zahlung", "transaktion", "buchung", "payment", "betrag"},
		"logging":        {"log", "protokoll", "audit", "nachvollzieh"},
		"crypto":         {"verschlüssel", "schlüssel", "krypto", "tls", "ssl", "hsm", "pin"},
		"api_security":   {"api", "schnittstelle", "authentifiz", "autorisier"},
		"terminal_comm":  {"terminal", "zvt", "opi", "gerät", "kontaktlos", "nfc"},
		"firmware":       {"firmware", "update", "signatur", "boot"},
		"reporting":      {"bericht", "report", "abrechnung", "export", "abgleich"},
		"access_control": {"zugang", "benutzer", "passwort", "rolle", "berechtigung"},
		"error_handling":  {"fehler", "ausfall", "recovery", "offline", "störung"},
		"build_deploy":   {"build", "deploy", "release", "ci", "pipeline"},
	}

	for domain, keywords := range domainKeywords {
		for _, kw := range keywords {
			if strings.Contains(textLower, kw) {
				return domain
			}
		}
	}
	return "general"
}

func inferCheckTarget(text string) string {
	textLower := strings.ToLower(text)
	if strings.Contains(textLower, "zertifik") || strings.Contains(textLower, "zulassung") {
		return "certificate"
	}
	if strings.Contains(textLower, "prozess") || strings.Contains(textLower, "verfahren") {
		return "process"
	}
	if strings.Contains(textLower, "konfigur") {
		return "config"
	}
	return "code"
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
