package fixers

import (
	"context"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"strings"

	"github.com/yourorg/devour/internal/quality"
	"github.com/yourorg/devour/internal/quality/plugins"
)

type DeadCodeFixer struct{}

func NewDeadCodeFixer() *DeadCodeFixer {
	return &DeadCodeFixer{}
}

func (f *DeadCodeFixer) Name() string {
	return "dead_code"
}

func (f *DeadCodeFixer) Description() string {
	return "Comments out or removes unused exported functions/types"
}

func (f *DeadCodeFixer) CanFix(finding quality.Finding) bool {
	return finding.Type == "dead_code" && finding.Severity == quality.SeverityT1
}

func (f *DeadCodeFixer) Fix(ctx context.Context, finding quality.Finding, dryRun bool) (*plugins.FixResult, error) {
	name := finding.Metadata["name"]
	if name == "" {
		return nil, fmt.Errorf("no function/type name in metadata")
	}

	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, finding.File, nil, parser.ParseComments)
	if err != nil {
		return nil, fmt.Errorf("parse error: %w", err)
	}

	if dryRun {
		return &plugins.FixResult{
			Success: true,
			Message: fmt.Sprintf("Would comment out unused '%s' in %s", name, finding.File),
		}, nil
	}

	var targetDecl ast.Decl
	for _, decl := range node.Decls {
		switch d := decl.(type) {
		case *ast.FuncDecl:
			if d.Name.Name == name {
				targetDecl = d
			}
		case *ast.GenDecl:
			for _, spec := range d.Specs {
				if ts, ok := spec.(*ast.TypeSpec); ok && ts.Name.Name == name {
					targetDecl = d
				}
			}
		}

		if targetDecl != nil {
			comment := &ast.CommentGroup{
				List: []*ast.Comment{
					{Text: "// DEPRECATED: This code is unused and should be removed"},
				},
			}

			if targetDecl.(*ast.FuncDecl) != nil {
				targetDecl.(*ast.FuncDecl).Doc = comment
			} else if targetDecl.(*ast.GenDecl) != nil {
				targetDecl.(*ast.GenDecl).Doc = comment
			}
			break
		}
	}

	if targetDecl == nil {
		return &plugins.FixResult{
			Success: false,
			Message: fmt.Sprintf("Could not find '%s' in file", name),
		}, nil
	}

	var output strings.Builder
	if err := format.Node(&output, fset, node); err != nil {
		return nil, fmt.Errorf("format error: %w", err)
	}

	if err := os.WriteFile(finding.File, []byte(output.String()), 0644); err != nil {
		return nil, fmt.Errorf("write error: %w", err)
	}

	return &plugins.FixResult{
		Success: true,
		Message: fmt.Sprintf("Marked '%s' as deprecated in %s", name, finding.File),
	}, nil
}

type ComplexityHintFixer struct{}

func NewComplexityHintFixer() *ComplexityHintFixer {
	return &ComplexityHintFixer{}
}

func (f *ComplexityHintFixer) Name() string {
	return "complexity_hint"
}

func (f *ComplexityHintFixer) Description() string {
	return "Adds complexity warning comments to complex functions"
}

func (f *ComplexityHintFixer) CanFix(finding quality.Finding) bool {
	return finding.Type == "complexity" || finding.Type == "complexity_ast"
}

func (f *ComplexityHintFixer) Fix(ctx context.Context, finding quality.Finding, dryRun bool) (*plugins.FixResult, error) {
	funcName := finding.Metadata["function"]
	if funcName == "" {
		return nil, fmt.Errorf("no function name in metadata")
	}

	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, finding.File, nil, parser.ParseComments)
	if err != nil {
		return nil, fmt.Errorf("parse error: %w", err)
	}

	if dryRun {
		return &plugins.FixResult{
			Success: true,
			Message: fmt.Sprintf("Would add complexity warning to '%s' in %s", funcName, finding.File),
		}, nil
	}

	for _, decl := range node.Decls {
		if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == funcName {
			complexity := finding.Metadata["complexity"]

			warning := fmt.Sprintf("// FIXME: High complexity (%s). Consider breaking into smaller functions.", complexity)

			comment := &ast.CommentGroup{
				List: []*ast.Comment{
					{Text: warning},
				},
			}
			fn.Doc = comment
			break
		}
	}

	var output strings.Builder
	if err := format.Node(&output, fset, node); err != nil {
		return nil, fmt.Errorf("format error: %w", err)
	}

	if err := os.WriteFile(finding.File, []byte(output.String()), 0644); err != nil {
		return nil, fmt.Errorf("write error: %w", err)
	}

	return &plugins.FixResult{
		Success: true,
		Message: fmt.Sprintf("Added complexity warning to '%s' in %s", funcName, finding.File),
	}, nil
}

type IoutilFixer struct{}

func NewIoutilFixer() *IoutilFixer {
	return &IoutilFixer{}
}

func (f *IoutilFixer) Name() string {
	return "ioutil"
}

func (f *IoutilFixer) Description() string {
	return "Replaces deprecated io/ioutil with modern equivalents"
}

func (f *IoutilFixer) CanFix(finding quality.Finding) bool {
	return finding.Type == "deprecated" && strings.Contains(finding.Title, "io/ioutil")
}

func (f *IoutilFixer) Fix(ctx context.Context, finding quality.Finding, dryRun bool) (*plugins.FixResult, error) {
	data, err := os.ReadFile(finding.File)
	if err != nil {
		return nil, fmt.Errorf("read error: %w", err)
	}

	content := string(data)

	replacements := map[string]string{
		`"io/ioutil"`:      "",
		`ioutil.ReadFile`:  `os.ReadFile`,
		`ioutil.WriteFile`: `os.WriteFile`,
		`ioutil.ReadDir`:   `os.ReadDir`,
		`ioutil.TempDir`:   `os.MkdirTemp`,
		`ioutil.TempFile`:  `os.CreateTemp`,
		`ioutil.NopCloser`: `io.NopCloser`,
		`ioutil.ReadAll`:   `io.ReadAll`,
		`ioutil.Discard`:   `io.Discard`,
	}

	if dryRun {
		return &plugins.FixResult{
			Success: true,
			Message: fmt.Sprintf("Would replace io/ioutil usage in %s", finding.File),
		}, nil
	}

	for old, new := range replacements {
		content = strings.ReplaceAll(content, old, new)
	}

	if strings.Contains(content, "os.ReadFile") || strings.Contains(content, "os.WriteFile") ||
		strings.Contains(content, "os.ReadDir") || strings.Contains(content, "os.MkdirTemp") ||
		strings.Contains(content, "os.CreateTemp") {
		if !strings.Contains(content, `"os"`) {
			content = strings.Replace(content, "package ", "import \"os\"\n\npackage ", 1)
		}
	}

	if strings.Contains(content, "io.NopCloser") || strings.Contains(content, "io.ReadAll") ||
		strings.Contains(content, "io.Discard") {
		if !strings.Contains(content, `"io"`) {
			content = strings.Replace(content, "package ", "import \"io\"\n\npackage ", 1)
		}
	}

	if err := os.WriteFile(finding.File, []byte(content), 0644); err != nil {
		return nil, fmt.Errorf("write error: %w", err)
	}

	return &plugins.FixResult{
		Success: true,
		Message: fmt.Sprintf("Replaced io/ioutil in %s", finding.File),
	}, nil
}

type DocCommentFixer struct{}

func NewDocCommentFixer() *DocCommentFixer {
	return &DocCommentFixer{}
}

func (f *DocCommentFixer) Name() string {
	return "doc_comment"
}

func (f *DocCommentFixer) Description() string {
	return "Adds TODO comments for missing documentation on exported items"
}

func (f *DocCommentFixer) CanFix(finding quality.Finding) bool {
	return finding.Type == "naming" || finding.Type == "god_struct" || finding.Type == "god_function"
}

func (f *DocCommentFixer) Fix(ctx context.Context, finding quality.Finding, dryRun bool) (*plugins.FixResult, error) {
	return &plugins.FixResult{
		Success: false,
		Message: "Documentation fixer requires manual intervention",
		Warnings: []string{
			fmt.Sprintf("Add documentation for: %s", finding.Title),
			fmt.Sprintf("Location: %s:%d", finding.File, finding.Line),
		},
	}, nil
}
