// Copyright (c) 2026 Alibaba Group and its affiliates

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdk

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"github.com/dashscope/dashscope-confidential-maas/pkg/attestation"
)

// TransparencyAuditRecord is persisted locally after each inference call,
// enabling offline transparency auditing and independent re-verification.
type TransparencyAuditRecord struct {
	RequestID    string                        `json:"request_id"`
	Timestamp    time.Time                     `json:"timestamp"`
	VerifiedAt   time.Time                     `json:"verified_at"`
	Model        string                        `json:"model"`
	Prompt       []Message                     `json:"prompt"`
	TEEType      string                        `json:"tee_type"`
	Evidence     json.RawMessage               `json:"evidence"`
	Transparency *attestation.TransparencyData `json:"transparency,omitempty"`
}

func defaultReportDir() (string, error) {
	const appName = "dashscope-confidential-maas"
	const subDir = "transparency-reports"

	switch runtime.GOOS {
	case "darwin":
		home, err := os.UserHomeDir()
		if err != nil {
			return "", err
		}
		return filepath.Join(home, "Library", "Application Support", appName, subDir), nil
	case "windows":
		appData := os.Getenv("APPDATA")
		if appData == "" {
			home, err := os.UserHomeDir()
			if err != nil {
				return "", err
			}
			appData = filepath.Join(home, "AppData", "Roaming")
		}
		return filepath.Join(appData, appName, subDir), nil
	default:
		dataDir := os.Getenv("XDG_DATA_HOME")
		if dataDir == "" {
			home, err := os.UserHomeDir()
			if err != nil {
				return "", err
			}
			dataDir = filepath.Join(home, ".local", "share")
		}
		return filepath.Join(dataDir, appName, subDir), nil
	}
}

// enrichEvidence merges TEE details JSON into the raw evidence under key "details".
// Falls back to raw evidence if merging fails.
func enrichEvidence(rawEvidence json.RawMessage, detailsJSON json.RawMessage) json.RawMessage {
	if len(detailsJSON) == 0 || len(rawEvidence) == 0 {
		return rawEvidence
	}
	var m map[string]any
	if err := json.Unmarshal(rawEvidence, &m); err != nil {
		return rawEvidence
	}
	var details any
	if err := json.Unmarshal(detailsJSON, &details); err != nil {
		return rawEvidence
	}
	m["details"] = details
	out, err := json.Marshal(m)
	if err != nil {
		return rawEvidence
	}
	return out
}

// reportWriter appends transparency audit records to a JSONL file, rotating
// the file on local-date change. One file per process (PID-suffixed) avoids
// cross-process write contention.
type reportWriter struct {
	mu          sync.Mutex
	dir         string
	pid         int
	currentDate string
	currentFile *os.File
}

func newReportWriter(dir string) *reportWriter {
	return &reportWriter{
		dir: dir,
		pid: os.Getpid(),
	}
}

// Write appends one record as a single JSONL line. Safe for concurrent callers.
func (w *reportWriter) Write(record *TransparencyAuditRecord) error {
	w.mu.Lock()
	defer w.mu.Unlock()

	dateStr := time.Now().Format("2006-01-02")
	if w.currentFile == nil || dateStr != w.currentDate {
		if w.currentFile != nil {
			_ = w.currentFile.Close()
			w.currentFile = nil
		}
		if err := os.MkdirAll(w.dir, 0700); err != nil {
			return fmt.Errorf("create report directory: %w", err)
		}
		filename := fmt.Sprintf("reports-%s-%d.jsonl", dateStr, w.pid)
		path := filepath.Join(w.dir, filename)
		f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
		if err != nil {
			return fmt.Errorf("open report file: %w", err)
		}
		w.currentFile = f
		w.currentDate = dateStr
	}

	data, err := json.Marshal(record)
	if err != nil {
		return fmt.Errorf("marshal report: %w", err)
	}
	data = append(data, '\n')
	if _, err := w.currentFile.Write(data); err != nil {
		return fmt.Errorf("write report: %w", err)
	}
	if err := w.currentFile.Sync(); err != nil {
		return fmt.Errorf("sync report: %w", err)
	}
	return nil
}

// Close releases the underlying file handle.
func (w *reportWriter) Close() error {
	w.mu.Lock()
	defer w.mu.Unlock()
	if w.currentFile == nil {
		return nil
	}
	err := w.currentFile.Close()
	w.currentFile = nil
	w.currentDate = ""
	return err
}

// saveTransparencyReport persists a transparency audit record to disk.
func (c *Client) saveTransparencyReport(messages []Message, session *Session) error {
	if c.reportWriter == nil || session == nil || session.attestation == nil {
		return nil
	}

	attest := session.attestation

	evidence := attest.Bundle.Evidence
	if v, ok := c.teeVerifiers[attest.Bundle.TEEType]; ok {
		if details, err := v.ExtractDetails(attest.Bundle.Evidence); err == nil {
			evidence = enrichEvidence(attest.Bundle.Evidence, details)
		}
	}

	record := &TransparencyAuditRecord{
		RequestID:    session.taskID,
		Timestamp:    time.Now(),
		VerifiedAt:   attest.VerifiedAt,
		Model:        c.model,
		Prompt:       messages,
		TEEType:      attest.Bundle.TEEType,
		Evidence:     evidence,
		Transparency: attest.Bundle.Transparency,
	}

	if err := c.reportWriter.Write(record); err != nil {
		log.Printf("[WARN] transparency report write failed task_id=%s error=%v", session.taskID, err)
		return err
	}
	return nil
}
